diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index a637c72927..172f28e3f9 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -6,7 +6,7 @@ on:
paths-ignore:
- 'docs/**'
- 'adr/**'
- branches: [ main, next ]
+ branches: [ main, next, v5.3 ]
push:
paths-ignore:
- 'docs/**'
@@ -14,6 +14,7 @@ on:
branches:
- main
- next
+ - v5.3
jobs:
sample_operators_tests:
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index e889a0be13..a6bd5c2032 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -11,7 +11,7 @@ on:
paths-ignore:
- 'docs/**'
- 'adr/**'
- branches: [ main, v1, v2, v3, next ]
+ branches: [ main, v1, v2, v3, next, v5.3 ]
workflow_dispatch:
jobs:
check_format_and_unit_tests:
diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml
index b3271c1c4f..364495fca9 100644
--- a/bootstrapper-maven-plugin/pom.xml
+++ b/bootstrapper-maven-plugin/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
bootstrapper
diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml
index 8d3546a718..a050e0ff3c 100644
--- a/caffeine-bounded-cache-support/pom.xml
+++ b/caffeine-bounded-cache-support/pom.xml
@@ -21,7 +21,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
caffeine-bounded-cache-support
diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml
index f1e174c856..93765e4a8a 100644
--- a/micrometer-support/pom.xml
+++ b/micrometer-support/pom.xml
@@ -21,7 +21,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
micrometer-support
diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml
index 2f3bef6fdf..1ffa1701f8 100644
--- a/operator-framework-bom/pom.xml
+++ b/operator-framework-bom/pom.xml
@@ -21,7 +21,7 @@
io.javaoperatorsdk
operator-framework-bom
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
pom
Operator SDK - Bill of Materials
Java SDK for implementing Kubernetes operators
diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index bb1baad08b..361bb3ad53 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -21,7 +21,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
../pom.xml
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
index 7aa29c98ae..b56676ac45 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
@@ -258,7 +258,7 @@ public
RegisteredController
register(
"Cannot register reconciler with name "
+ reconciler.getClass().getCanonicalName()
+ " reconciler named "
- + ReconcilerUtils.getNameFor(reconciler)
+ + ReconcilerUtilsInternal.getNameFor(reconciler)
+ " because its configuration cannot be found.\n"
+ " Known reconcilers are: "
+ configurationService.getKnownReconcilerNames());
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternal.java
similarity index 99%
rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternal.java
index 354c2aa420..1523b792a5 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternal.java
@@ -34,7 +34,7 @@
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
@SuppressWarnings("rawtypes")
-public class ReconcilerUtils {
+public class ReconcilerUtilsInternal {
private static final String FINALIZER_NAME_SUFFIX = "/finalizer";
protected static final String MISSING_GROUP_SUFFIX = ".javaoperatorsdk.io";
@@ -46,7 +46,7 @@ public class ReconcilerUtils {
Pattern.compile(".*http(s?)://[^/]*/api(s?)/(\\S*).*"); // NOSONAR: input is controlled
// prevent instantiation of util class
- private ReconcilerUtils() {}
+ private ReconcilerUtilsInternal() {}
public static boolean isFinalizerValid(String finalizer) {
return HasMetadata.validateFinalizer(finalizer);
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java
index b85ee03fcb..a1b37d6fe9 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java
@@ -22,7 +22,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
/**
@@ -145,7 +145,7 @@ private String getReconcilersNameMessage() {
}
protected String keyFor(Reconciler reconciler) {
- return ReconcilerUtils.getNameFor(reconciler);
+ return ReconcilerUtilsInternal.getNameFor(reconciler);
}
@SuppressWarnings("unused")
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
index 18ef8b598c..ca14043750 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java
@@ -28,7 +28,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
@@ -265,7 +265,7 @@ private ResolvedControllerConfiguration
controllerCon
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
final var resourceClass = getResourceClassResolver().getPrimaryResourceClass(reconcilerClass);
- final var name = ReconcilerUtils.getNameFor(reconcilerClass);
+ final var name = ReconcilerUtilsInternal.getNameFor(reconcilerClass);
final var generationAware =
valueOrDefaultFromAnnotation(
annotation,
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
index 6215c20179..6ed9b7ff64 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
@@ -28,8 +28,6 @@
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Secret;
-import io.fabric8.kubernetes.api.model.apps.Deployment;
-import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.CustomResource;
@@ -447,64 +445,6 @@ default Set> defaultNonSSAResource() {
return defaultNonSSAResources();
}
- /**
- * If a javaoperatorsdk.io/previous annotation should be used so that the operator sdk can detect
- * events from its own updates of dependent resources and then filter them.
- *
- * Disable this if you want to react to your own dependent resource updates
- *
- * @return if special annotation should be used for dependent resource to filter events
- * @since 4.5.0
- */
- default boolean previousAnnotationForDependentResourcesEventFiltering() {
- return true;
- }
-
- /**
- * For dependent resources, the framework can add an annotation to filter out events resulting
- * directly from the framework's operation. There are, however, some resources that do not follow
- * the Kubernetes API conventions that changes in metadata should not increase the generation of
- * the resource (as recorded in the {@code generation} field of the resource's {@code metadata}).
- * For these resources, this convention is not respected and results in a new event for the
- * framework to process. If that particular case is not handled correctly in the resource matcher,
- * the framework will consider that the resource doesn't match the desired state and therefore
- * triggers an update, which in turn, will re-add the annotation, thus starting the loop again,
- * infinitely.
- *
- *
As a workaround, we automatically skip adding previous annotation for those well-known
- * resources. Note that if you are sure that the matcher works for your use case, and it should in
- * most instances, you can remove the resource type from the blocklist.
- *
- *
The consequence of adding a resource type to the set is that the framework will not use
- * event filtering to prevent events, initiated by changes made by the framework itself as a
- * result of its processing of dependent resources, to trigger the associated reconciler again.
- *
- *
Note that this method only takes effect if annotating dependent resources to prevent
- * dependent resources events from triggering the associated reconciler again is activated as
- * controlled by {@link #previousAnnotationForDependentResourcesEventFiltering()}
- *
- * @return a Set of resource classes where the previous version annotation won't be used.
- */
- default Set> withPreviousAnnotationForDependentResourcesBlocklist() {
- return Set.of(Deployment.class, StatefulSet.class);
- }
-
- /**
- * If the event logic should parse the resourceVersion to determine the ordering of dependent
- * resource events. This is typically not needed.
- *
- * Disabled by default as Kubernetes does not support, and discourages, this interpretation of
- * resourceVersions. Enable only if your api server event processing seems to lag the operator
- * logic, and you want to further minimize the amount of work done / updates issued by the
- * operator.
- *
- * @return if resource version should be parsed (as integer)
- * @since 4.5.0
- */
- default boolean parseResourceVersionsForEventFilteringAndCaching() {
- return false;
- }
-
/**
* {@link io.javaoperatorsdk.operator.api.reconciler.UpdateControl} patch resource or status can
* either use simple patches or SSA. Setting this to {@code true}, controllers will use SSA for
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
index 3d29bb6589..cd9cdafb39 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
@@ -51,11 +51,8 @@ public class ConfigurationServiceOverrider {
private Duration reconciliationTerminationTimeout;
private Boolean ssaBasedCreateUpdateMatchForDependentResources;
private Set> defaultNonSSAResource;
- private Boolean previousAnnotationForDependentResources;
- private Boolean parseResourceVersions;
private Boolean useSSAToPatchPrimaryResource;
private Boolean cloneSecondaryResourcesWhenGettingFromCache;
- private Set> previousAnnotationUsageBlocklist;
@SuppressWarnings("rawtypes")
private DependentResourceFactory dependentResourceFactory;
@@ -168,31 +165,6 @@ public ConfigurationServiceOverrider withDefaultNonSSAResource(
return this;
}
- public ConfigurationServiceOverrider withPreviousAnnotationForDependentResources(boolean value) {
- this.previousAnnotationForDependentResources = value;
- return this;
- }
-
- /**
- * @param value true if internal algorithms can use metadata.resourceVersion as a numeric value.
- * @return this
- */
- public ConfigurationServiceOverrider withParseResourceVersions(boolean value) {
- this.parseResourceVersions = value;
- return this;
- }
-
- /**
- * @deprecated use withParseResourceVersions
- * @param value true if internal algorithms can use metadata.resourceVersion as a numeric value.
- * @return this
- */
- @Deprecated(forRemoval = true)
- public ConfigurationServiceOverrider wihtParseResourceVersions(boolean value) {
- this.parseResourceVersions = value;
- return this;
- }
-
public ConfigurationServiceOverrider withUseSSAToPatchPrimaryResource(boolean value) {
this.useSSAToPatchPrimaryResource = value;
return this;
@@ -204,12 +176,6 @@ public ConfigurationServiceOverrider withCloneSecondaryResourcesWhenGettingFromC
return this;
}
- public ConfigurationServiceOverrider withPreviousAnnotationForDependentResourcesBlocklist(
- Set> blocklist) {
- this.previousAnnotationUsageBlocklist = blocklist;
- return this;
- }
-
public ConfigurationService build() {
return new BaseConfigurationService(original.getVersion(), cloner, client) {
@Override
@@ -331,20 +297,6 @@ public Set> defaultNonSSAResources() {
defaultNonSSAResource, ConfigurationService::defaultNonSSAResources);
}
- @Override
- public boolean previousAnnotationForDependentResourcesEventFiltering() {
- return overriddenValueOrDefault(
- previousAnnotationForDependentResources,
- ConfigurationService::previousAnnotationForDependentResourcesEventFiltering);
- }
-
- @Override
- public boolean parseResourceVersionsForEventFilteringAndCaching() {
- return overriddenValueOrDefault(
- parseResourceVersions,
- ConfigurationService::parseResourceVersionsForEventFilteringAndCaching);
- }
-
@Override
public boolean useSSAToPatchPrimaryResource() {
return overriddenValueOrDefault(
@@ -357,14 +309,6 @@ public boolean cloneSecondaryResourcesWhenGettingFromCache() {
cloneSecondaryResourcesWhenGettingFromCache,
ConfigurationService::cloneSecondaryResourcesWhenGettingFromCache);
}
-
- @Override
- public Set>
- withPreviousAnnotationForDependentResourcesBlocklist() {
- return overriddenValueOrDefault(
- previousAnnotationUsageBlocklist,
- ConfigurationService::withPreviousAnnotationForDependentResourcesBlocklist);
- }
};
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
index a601092454..e0834621fc 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
@@ -20,7 +20,7 @@
import java.util.Set;
import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval;
@@ -42,16 +42,18 @@ default String getName() {
}
default String getFinalizerName() {
- return ReconcilerUtils.getDefaultFinalizerName(getResourceClass());
+ return ReconcilerUtilsInternal.getDefaultFinalizerName(getResourceClass());
}
static String ensureValidName(String name, String reconcilerClassName) {
- return name != null ? name : ReconcilerUtils.getDefaultReconcilerName(reconcilerClassName);
+ return name != null
+ ? name
+ : ReconcilerUtilsInternal.getDefaultReconcilerName(reconcilerClassName);
}
static String ensureValidFinalizerName(String finalizer, String resourceTypeName) {
if (finalizer != null && !finalizer.isBlank()) {
- if (ReconcilerUtils.isFinalizerValid(finalizer)) {
+ if (ReconcilerUtilsInternal.isFinalizerValid(finalizer)) {
return finalizer;
} else {
throw new IllegalArgumentException(
@@ -61,7 +63,7 @@ static String ensureValidFinalizerName(String finalizer, String resourceTypeName
+ " for details");
}
} else {
- return ReconcilerUtils.getDefaultFinalizerName(resourceTypeName);
+ return ReconcilerUtilsInternal.getDefaultFinalizerName(resourceTypeName);
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/Informer.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/Informer.java
index 9264db66bc..e6655641a2 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/Informer.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/Informer.java
@@ -28,6 +28,7 @@
import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
+import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_COMPARABLE_RESOURCE_VERSION;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_FOLLOW_CONTROLLER_NAMESPACE_CHANGES;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_LONG_VALUE_SET;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET;
@@ -131,4 +132,11 @@
/** Kubernetes field selector for additional resource filtering */
Field[] fieldSelector() default {};
+
+ /**
+ * true if we can consider resource versions as integers, therefore it is valid to compare them
+ *
+ * @since 5.3.0
+ */
+ boolean comparableResourceVersions() default DEFAULT_COMPARABLE_RESOURCE_VERSION;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java
index 24f78eb7be..f6caa4fe4d 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java
@@ -25,7 +25,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.Utils;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
@@ -53,6 +53,7 @@ public class InformerConfiguration {
private ItemStore itemStore;
private Long informerListLimit;
private FieldSelector fieldSelector;
+ private boolean comparableResourceVersions;
protected InformerConfiguration(
Class resourceClass,
@@ -66,7 +67,8 @@ protected InformerConfiguration(
GenericFilter super R> genericFilter,
ItemStore itemStore,
Long informerListLimit,
- FieldSelector fieldSelector) {
+ FieldSelector fieldSelector,
+ boolean comparableResourceVersions) {
this(resourceClass);
this.name = name;
this.namespaces = namespaces;
@@ -79,6 +81,7 @@ protected InformerConfiguration(
this.itemStore = itemStore;
this.informerListLimit = informerListLimit;
this.fieldSelector = fieldSelector;
+ this.comparableResourceVersions = comparableResourceVersions;
}
private InformerConfiguration(Class resourceClass) {
@@ -89,7 +92,7 @@ private InformerConfiguration(Class resourceClass) {
// controller
// where GenericKubernetesResource now does not apply
? GenericKubernetesResource.class.getSimpleName()
- : ReconcilerUtils.getResourceTypeName(resourceClass);
+ : ReconcilerUtilsInternal.getResourceTypeName(resourceClass);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@@ -113,7 +116,8 @@ public static InformerConfiguration.Builder builder(
original.genericFilter,
original.itemStore,
original.informerListLimit,
- original.fieldSelector)
+ original.fieldSelector,
+ original.comparableResourceVersions)
.builder;
}
@@ -288,6 +292,10 @@ public FieldSelector getFieldSelector() {
return fieldSelector;
}
+ public boolean isComparableResourceVersions() {
+ return comparableResourceVersions;
+ }
+
@SuppressWarnings("UnusedReturnValue")
public class Builder {
@@ -359,6 +367,7 @@ public InformerConfiguration.Builder initFromAnnotation(
Arrays.stream(informerConfig.fieldSelector())
.map(f -> new FieldSelector.Field(f.path(), f.value(), f.negated()))
.toList()));
+ withComparableResourceVersions(informerConfig.comparableResourceVersions());
}
return this;
}
@@ -459,5 +468,10 @@ public Builder withFieldSelector(FieldSelector fieldSelector) {
InformerConfiguration.this.fieldSelector = fieldSelector;
return this;
}
+
+ public Builder withComparableResourceVersions(boolean comparableResourceVersions) {
+ InformerConfiguration.this.comparableResourceVersions = comparableResourceVersions;
+ return this;
+ }
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java
index bca605a41c..69903e805f 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java
@@ -33,6 +33,7 @@
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers;
+import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_COMPARABLE_RESOURCE_VERSION;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.SAME_AS_CONTROLLER_NAMESPACES_SET;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_ALL_NAMESPACE_SET;
import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_CURRENT_NAMESPACE_SET;
@@ -96,18 +97,21 @@ class DefaultInformerEventSourceConfiguration
private final GroupVersionKind groupVersionKind;
private final InformerConfiguration informerConfig;
private final KubernetesClient kubernetesClient;
+ private final boolean comparableResourceVersion;
protected DefaultInformerEventSourceConfiguration(
GroupVersionKind groupVersionKind,
PrimaryToSecondaryMapper> primaryToSecondaryMapper,
SecondaryToPrimaryMapper secondaryToPrimaryMapper,
InformerConfiguration informerConfig,
- KubernetesClient kubernetesClient) {
+ KubernetesClient kubernetesClient,
+ boolean comparableResourceVersion) {
this.informerConfig = Objects.requireNonNull(informerConfig);
this.groupVersionKind = groupVersionKind;
this.primaryToSecondaryMapper = primaryToSecondaryMapper;
this.secondaryToPrimaryMapper = secondaryToPrimaryMapper;
this.kubernetesClient = kubernetesClient;
+ this.comparableResourceVersion = comparableResourceVersion;
}
@Override
@@ -135,6 +139,11 @@ public Optional getGroupVersionKind() {
public Optional getKubernetesClient() {
return Optional.ofNullable(kubernetesClient);
}
+
+ @Override
+ public boolean comparableResourceVersion() {
+ return this.comparableResourceVersion;
+ }
}
@SuppressWarnings({"unused", "UnusedReturnValue"})
@@ -148,6 +157,7 @@ class Builder {
private PrimaryToSecondaryMapper> primaryToSecondaryMapper;
private SecondaryToPrimaryMapper secondaryToPrimaryMapper;
private KubernetesClient kubernetesClient;
+ private boolean comparableResourceVersion = DEFAULT_COMPARABLE_RESOURCE_VERSION;
private Builder(Class resourceClass, Class extends HasMetadata> primaryResourceClass) {
this(resourceClass, primaryResourceClass, null);
@@ -285,6 +295,11 @@ public Builder withFieldSelector(FieldSelector fieldSelector) {
return this;
}
+ public Builder withComparableResourceVersion(boolean comparableResourceVersion) {
+ this.comparableResourceVersion = comparableResourceVersion;
+ return this;
+ }
+
public void updateFrom(InformerConfiguration informerConfig) {
if (informerConfig != null) {
final var informerConfigName = informerConfig.getName();
@@ -324,7 +339,10 @@ public InformerEventSourceConfiguration build() {
HasMetadata.getKind(primaryResourceClass),
false)),
config.build(),
- kubernetesClient);
+ kubernetesClient,
+ comparableResourceVersion);
}
}
+
+ boolean comparableResourceVersion();
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
index 052b4d8c44..7330a407c1 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
@@ -41,6 +41,7 @@ public final class Constants {
public static final String RESOURCE_GVK_KEY = "josdk.resource.gvk";
public static final String CONTROLLER_NAME = "controller.name";
public static final boolean DEFAULT_FOLLOW_CONTROLLER_NAMESPACE_CHANGES = true;
+ public static final boolean DEFAULT_COMPARABLE_RESOURCE_VERSION = true;
private Constants() {}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
index f3fade4659..f1aeadd52a 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
@@ -15,15 +15,19 @@
*/
package io.javaoperatorsdk.operator.api.reconciler;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext;
import io.javaoperatorsdk.operator.processing.Controller;
@@ -41,6 +45,7 @@ public class DefaultContext implements Context
{
defaultManagedDependentResourceContext;
private final boolean primaryResourceDeleted;
private final boolean primaryResourceFinalStateUnknown;
+ private final Map, Object> desiredStates = new ConcurrentHashMap<>();
public DefaultContext(
RetryInfo retryInfo,
@@ -157,4 +162,12 @@ public DefaultContext setRetryInfo(RetryInfo retryInfo) {
this.retryInfo = retryInfo;
return this;
}
+
+ @SuppressWarnings("unchecked")
+ public R getOrComputeDesiredStateFor(
+ DependentResource dependentResource, Function desiredStateComputer) {
+ return (R)
+ desiredStates.computeIfAbsent(
+ dependentResource, ignored -> desiredStateComputer.apply(getPrimaryResource()));
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtils.java
new file mode 100644
index 0000000000..8c240fabf7
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtils.java
@@ -0,0 +1,987 @@
+/*
+ * Copyright Java Operator SDK Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.javaoperatorsdk.operator.api.reconciler;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+
+import org.slf4j.Logger;
+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 io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource;
+
+import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID;
+import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;
+
+public class ReconcileUtils {
+
+ private static final Logger log = LoggerFactory.getLogger(ReconcileUtils.class);
+
+ public static final int DEFAULT_MAX_RETRY = 10;
+
+ private ReconcileUtils() {}
+
+ // todo move finalizers mtehods & deprecate
+ // todo test namespace handling
+ // todo compare resource version if multiple event sources provide the same resource
+
+ /**
+ * Server-Side Apply the resource and filters out the resulting event. This is a convenience
+ * method that calls {@link #serverSideApply(Context, HasMetadata, boolean)} with filterEvent set
+ * to true.
+ *
+ * @param context of reconciler
+ * @param resource fresh resource for server side apply
+ * @return updated resource
+ * @param resource type
+ * @see #serverSideApply(Context, HasMetadata, boolean)
+ */
+ public static R serverSideApply(
+ Context extends HasMetadata> context, R resource) {
+ return serverSideApply(context, resource, true);
+ }
+
+ /**
+ * Updates the resource and caches the response if needed, thus making sure that next
+ * reconciliation will contain to updated resource. Or more recent one if someone did an update
+ * after our update.
+ *
+ * Optionally also can filter out the event, what is the result of this update.
+ *
+ *
You are free to control the optimistic locking by setting the resource version in resource
+ * metadata. In case of SSA we advise not to do updates with optimistic locking.
+ *
+ * @param context of reconciler
+ * @param resource fresh resource for server side apply
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R serverSideApply(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ context,
+ resource,
+ r ->
+ context
+ .getClient()
+ .resource(r)
+ .patch(
+ new PatchContext.Builder()
+ .withForce(true)
+ .withFieldManager(context.getControllerConfiguration().fieldManager())
+ .withPatchType(PatchType.SERVER_SIDE_APPLY)
+ .build()),
+ filterEvent);
+ }
+
+ /**
+ * Server-Side Apply the resource status subresource and filters out the resulting event. This is
+ * a convenience method that calls {@link #serverSideApplyStatus(Context, HasMetadata, boolean)}
+ * with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource fresh resource for server side apply
+ * @return updated resource
+ * @param resource type
+ * @see #serverSideApplyStatus(Context, HasMetadata, boolean)
+ */
+ public static R serverSideApplyStatus(
+ Context extends HasMetadata> context, R resource) {
+ return serverSideApplyStatus(context, resource, true);
+ }
+
+ /**
+ * Server-Side Apply the resource status subresource. Updates the resource status and caches the
+ * response if needed, ensuring the next reconciliation will contain the updated resource.
+ *
+ * @param context of reconciler
+ * @param resource fresh resource for server side apply
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R serverSideApplyStatus(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ context,
+ resource,
+ r ->
+ context
+ .getClient()
+ .resource(r)
+ .subresource("status")
+ .patch(
+ new PatchContext.Builder()
+ .withForce(true)
+ .withFieldManager(context.getControllerConfiguration().fieldManager())
+ .withPatchType(PatchType.SERVER_SIDE_APPLY)
+ .build()),
+ filterEvent);
+ }
+
+ /**
+ * Server-Side Apply the primary resource and filters out the resulting event. This is a
+ * convenience method that calls {@link #serverSideApplyPrimary(Context, HasMetadata, boolean)}
+ * with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource for server side apply
+ * @return updated resource
+ * @param primary resource type
+ * @see #serverSideApplyPrimary(Context, HasMetadata, boolean)
+ */
+ public static
P serverSideApplyPrimary(Context
context, P resource) {
+ return serverSideApplyPrimary(context, resource, true);
+ }
+
+ /**
+ * Server-Side Apply the primary resource. Updates the primary resource and caches the response
+ * using the controller's event source, ensuring the next reconciliation will contain the updated
+ * resource.
+ *
+ * @param context of reconciler
+ * @param resource primary resource for server side apply
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param
primary resource type
+ */
+ public static
P serverSideApplyPrimary(
+ Context
context, P resource, boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r ->
+ context
+ .getClient()
+ .resource(r)
+ .patch(
+ new PatchContext.Builder()
+ .withForce(true)
+ .withFieldManager(context.getControllerConfiguration().fieldManager())
+ .withPatchType(PatchType.SERVER_SIDE_APPLY)
+ .build()),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Server-Side Apply the primary resource status subresource and filters out the resulting event.
+ * This is a convenience method that calls {@link #serverSideApplyPrimaryStatus(Context,
+ * HasMetadata, boolean)} with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource for server side apply
+ * @return updated resource
+ * @param
primary resource type
+ * @see #serverSideApplyPrimaryStatus(Context, HasMetadata, boolean)
+ */
+ public static
P serverSideApplyPrimaryStatus(
+ Context
context, P resource) {
+ return serverSideApplyPrimaryStatus(context, resource, true);
+ }
+
+ /**
+ * Server-Side Apply the primary resource status subresource. Updates the primary resource status
+ * and caches the response using the controller's event source.
+ *
+ * @param context of reconciler
+ * @param resource primary resource for server side apply
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param
primary resource type
+ */
+ public static
P serverSideApplyPrimaryStatus(
+ Context
context, P resource, boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r ->
+ context
+ .getClient()
+ .resource(r)
+ .subresource("status")
+ .patch(
+ new PatchContext.Builder()
+ .withForce(true)
+ .withFieldManager(context.getControllerConfiguration().fieldManager())
+ .withPatchType(PatchType.SERVER_SIDE_APPLY)
+ .build()),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Updates the resource and filters out the resulting event. This is a convenience method that
+ * calls {@link #update(Context, HasMetadata, boolean)} with filterEvent set to true. Uses
+ * optimistic locking based on the resource version.
+ *
+ * @param context of reconciler
+ * @param resource resource to update
+ * @return updated resource
+ * @param resource type
+ * @see #update(Context, HasMetadata, boolean)
+ */
+ public static R update(
+ Context extends HasMetadata> context, R resource) {
+ return update(context, resource, true);
+ }
+
+ /**
+ * Updates the resource with optimistic locking based on the resource version. Caches the response
+ * if needed, ensuring the next reconciliation will contain the updated resource.
+ *
+ * @param context of reconciler
+ * @param resource resource to update
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R update(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ context, resource, r -> context.getClient().resource(r).update(), filterEvent);
+ }
+
+ /**
+ * Updates the resource status subresource and filters out the resulting event. This is a
+ * convenience method that calls {@link #updateStatus(Context, HasMetadata, boolean)} with
+ * filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource resource to update
+ * @return updated resource
+ * @param resource type
+ * @see #updateStatus(Context, HasMetadata, boolean)
+ */
+ public static R updateStatus(
+ Context extends HasMetadata> context, R resource) {
+ return updateStatus(context, resource, true);
+ }
+
+ /**
+ * Updates the resource status subresource with optimistic locking. Caches the response if needed.
+ *
+ * @param context of reconciler
+ * @param resource resource to update
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R updateStatus(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ context, resource, r -> context.getClient().resource(r).updateStatus(), filterEvent);
+ }
+
+ /**
+ * Updates the primary resource and filters out the resulting event. This is a convenience method
+ * that calls {@link #updatePrimary(Context, HasMetadata, boolean)} with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to update
+ * @return updated resource
+ * @param resource type
+ * @see #updatePrimary(Context, HasMetadata, boolean)
+ */
+ public static R updatePrimary(
+ Context extends HasMetadata> context, R resource) {
+ return updatePrimary(context, resource, true);
+ }
+
+ /**
+ * Updates the primary resource with optimistic locking. Caches the response using the
+ * controller's event source.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to update
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R updatePrimary(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r -> context.getClient().resource(r).update(),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Updates the primary resource status subresource and filters out the resulting event. This is a
+ * convenience method that calls {@link #updatePrimaryStatus(Context, HasMetadata, boolean)} with
+ * filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to update
+ * @return updated resource
+ * @param resource type
+ * @see #updatePrimaryStatus(Context, HasMetadata, boolean)
+ */
+ public static R updatePrimaryStatus(
+ Context extends HasMetadata> context, R resource) {
+ return updatePrimaryStatus(context, resource, true);
+ }
+
+ /**
+ * Updates the primary resource status subresource with optimistic locking. Caches the response
+ * using the controller's event source.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to update
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R updatePrimaryStatus(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r -> context.getClient().resource(r).updateStatus(),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Applies a JSON Patch to the resource and filters out the resulting event. This is a convenience
+ * method that calls {@link #jsonPatch(Context, HasMetadata, UnaryOperator, boolean)} with
+ * filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @param unaryOperator function to modify the resource
+ * @return updated resource
+ * @param resource type
+ * @see #jsonPatch(Context, HasMetadata, UnaryOperator, boolean)
+ */
+ public static R jsonPatch(
+ Context extends HasMetadata> context, R resource, UnaryOperator unaryOperator) {
+ return jsonPatch(context, resource, unaryOperator, true);
+ }
+
+ /**
+ * Applies a JSON Patch to the resource. The unaryOperator function is used to modify the
+ * resource, and the differences are sent as a JSON Patch to the Kubernetes API server.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @param unaryOperator function to modify the resource
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonPatch(
+ Context extends HasMetadata> context,
+ R resource,
+ UnaryOperator unaryOperator,
+ boolean filterEvent) {
+ return resourcePatch(
+ context, resource, r -> context.getClient().resource(r).edit(unaryOperator), filterEvent);
+ }
+
+ /**
+ * Applies a JSON Patch to the resource status subresource and filters out the resulting event.
+ * This is a convenience method that calls {@link #jsonPatchStatus(Context, HasMetadata,
+ * UnaryOperator, boolean)} with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @param unaryOperator function to modify the resource
+ * @return updated resource
+ * @param resource type
+ * @see #jsonPatchStatus(Context, HasMetadata, UnaryOperator, boolean)
+ */
+ public static R jsonPatchStatus(
+ Context extends HasMetadata> context, R resource, UnaryOperator unaryOperator) {
+ return jsonPatchStatus(context, resource, unaryOperator, true);
+ }
+
+ /**
+ * Applies a JSON Patch to the resource status subresource. The unaryOperator function is used to
+ * modify the resource status, and the differences are sent as a JSON Patch.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @param unaryOperator function to modify the resource
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonPatchStatus(
+ Context extends HasMetadata> context,
+ R resource,
+ UnaryOperator unaryOperator,
+ boolean filterEvent) {
+ return resourcePatch(
+ context,
+ resource,
+ r -> context.getClient().resource(r).editStatus(unaryOperator),
+ filterEvent);
+ }
+
+ /**
+ * Applies a JSON Patch to the primary resource and filters out the resulting event. This is a
+ * convenience method that calls {@link #jsonPatchPrimary(Context, HasMetadata, UnaryOperator,
+ * boolean)} with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @param unaryOperator function to modify the resource
+ * @return updated resource
+ * @param resource type
+ * @see #jsonPatchPrimary(Context, HasMetadata, UnaryOperator, boolean)
+ */
+ public static R jsonPatchPrimary(
+ Context extends HasMetadata> context, R resource, UnaryOperator unaryOperator) {
+ return jsonPatchPrimary(context, resource, unaryOperator, true);
+ }
+
+ /**
+ * Applies a JSON Patch to the primary resource. Caches the response using the controller's event
+ * source.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @param unaryOperator function to modify the resource
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonPatchPrimary(
+ Context extends HasMetadata> context,
+ R resource,
+ UnaryOperator unaryOperator,
+ boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r -> context.getClient().resource(r).edit(unaryOperator),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Applies a JSON Patch to the primary resource status subresource and filters out the resulting
+ * event. This is a convenience method that calls {@link #jsonPatchPrimaryStatus(Context,
+ * HasMetadata, UnaryOperator, boolean)} with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @param unaryOperator function to modify the resource
+ * @return updated resource
+ * @param resource type
+ * @see #jsonPatchPrimaryStatus(Context, HasMetadata, UnaryOperator, boolean)
+ */
+ public static R jsonPatchPrimaryStatus(
+ Context extends HasMetadata> context, R resource, UnaryOperator unaryOperator) {
+ return jsonPatchPrimaryStatus(context, resource, unaryOperator, true);
+ }
+
+ /**
+ * Applies a JSON Patch to the primary resource status subresource. Caches the response using the
+ * controller's event source.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @param unaryOperator function to modify the resource
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonPatchPrimaryStatus(
+ Context extends HasMetadata> context,
+ R resource,
+ UnaryOperator unaryOperator,
+ boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r -> context.getClient().resource(r).editStatus(unaryOperator),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the resource and filters out the resulting event. This is a
+ * convenience method that calls {@link #jsonMergePatch(Context, HasMetadata, boolean)} with
+ * filterEvent set to true. JSON Merge Patch (RFC 7386) is a simpler patching strategy compared to
+ * JSON Patch.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @return updated resource
+ * @param resource type
+ * @see #jsonMergePatch(Context, HasMetadata, boolean)
+ */
+ public static R jsonMergePatch(
+ Context extends HasMetadata> context, R resource) {
+ return jsonMergePatch(context, resource, true);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the resource. JSON Merge Patch (RFC 7386) is a simpler patching
+ * strategy that merges the provided resource with the existing resource on the server.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonMergePatch(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ context, resource, r -> context.getClient().resource(r).patch(), filterEvent);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the resource status subresource and filters out the resulting
+ * event. This is a convenience method that calls {@link #jsonMergePatchStatus(Context,
+ * HasMetadata, boolean)} with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @return updated resource
+ * @param resource type
+ * @see #jsonMergePatchStatus(Context, HasMetadata, boolean)
+ */
+ public static R jsonMergePatchStatus(
+ Context extends HasMetadata> context, R resource) {
+ return jsonMergePatchStatus(context, resource, true);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the resource status subresource. Merges the provided resource
+ * status with the existing resource status on the server.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonMergePatchStatus(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ context, resource, r -> context.getClient().resource(r).patchStatus(), filterEvent);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the primary resource and filters out the resulting event. This is
+ * a convenience method that calls {@link #jsonMergePatchPrimary(Context, HasMetadata, boolean)}
+ * with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @return updated resource
+ * @param resource type
+ * @see #jsonMergePatchPrimary(Context, HasMetadata, boolean)
+ */
+ public static R jsonMergePatchPrimary(
+ Context extends HasMetadata> context, R resource) {
+ return jsonMergePatchPrimary(context, resource, true);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the primary resource. Caches the response using the controller's
+ * event source.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonMergePatchPrimary(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r -> context.getClient().resource(r).patch(),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the primary resource status subresource and filters out the
+ * resulting event. This is a convenience method that calls {@link
+ * #jsonMergePatchPrimaryStatus(Context, HasMetadata, boolean)} with filterEvent set to true.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @return updated resource
+ * @param resource type
+ * @see #jsonMergePatchPrimaryStatus(Context, HasMetadata, boolean)
+ */
+ public static R jsonMergePatchPrimaryStatus(
+ Context extends HasMetadata> context, R resource) {
+ return jsonMergePatchPrimaryStatus(context, resource, true);
+ }
+
+ /**
+ * Applies a JSON Merge Patch to the primary resource status subresource. Caches the response
+ * using the controller's event source.
+ *
+ * @param context of reconciler
+ * @param resource primary resource to patch
+ * @param filterEvent if true the event from this update will be filtered out so won't trigger the
+ * reconciliation
+ * @return updated resource
+ * @param resource type
+ */
+ public static R jsonMergePatchPrimaryStatus(
+ Context extends HasMetadata> context, R resource, boolean filterEvent) {
+ return resourcePatch(
+ resource,
+ r -> context.getClient().resource(r).patch(),
+ context.eventSourceRetriever().getControllerEventSource(),
+ filterEvent);
+ }
+
+ /**
+ * Internal utility method to patch a resource and cache the result. Automatically discovers the
+ * event source for the resource type and delegates to {@link #resourcePatch(HasMetadata,
+ * UnaryOperator, ManagedInformerEventSource, boolean)}.
+ *
+ * @param context of reconciler
+ * @param resource resource to patch
+ * @param updateOperation operation to perform (update, patch, edit, etc.)
+ * @param filterEvent if true the event from this update will be filtered out
+ * @return updated resource
+ * @param resource type
+ * @throws IllegalStateException if no event source or multiple event sources are found
+ */
+ public static R resourcePatch(
+ Context> context, R resource, UnaryOperator updateOperation, boolean filterEvent) {
+
+ var esList = context.eventSourceRetriever().getEventSourcesFor(resource.getClass());
+ if (esList.isEmpty()) {
+ throw new IllegalStateException("No event source found for type: " + resource.getClass());
+ }
+ if (esList.size() > 1) {
+ throw new IllegalStateException(
+ "Multiple event sources found for: "
+ + resource.getClass()
+ + " please provide the target event source");
+ }
+ var es = esList.get(0);
+ if (es instanceof ManagedInformerEventSource mes) {
+ return resourcePatch(resource, updateOperation, mes, filterEvent);
+ } else {
+ throw new IllegalStateException(
+ "Target event source must be a subclass off "
+ + ManagedInformerEventSource.class.getName());
+ }
+ }
+
+ /**
+ * Internal utility method to patch a resource and cache the result using the specified event
+ * source. This method either filters out the resulting event or allows it to trigger
+ * reconciliation based on the filterEvent parameter.
+ *
+ * @param resource resource to patch
+ * @param updateOperation operation to perform (update, patch, edit, etc.)
+ * @param ies the managed informer event source to use for caching
+ * @param filterEvent if true the event from this update will be filtered out
+ * @return updated resource
+ * @param resource type
+ */
+ @SuppressWarnings("unchecked")
+ public static R resourcePatch(
+ R resource,
+ UnaryOperator updateOperation,
+ ManagedInformerEventSource ies,
+ boolean filterEvent) {
+ return filterEvent
+ ? (R) ies.eventFilteringUpdateAndCacheResource(resource, updateOperation)
+ : (R) ies.updateAndCacheResource(resource, updateOperation);
+ }
+
+ /**
+ * Adds the default finalizer (from controller configuration) to the primary resource. This is a
+ * convenience method that calls {@link #addFinalizer(Context, String)} with the configured
+ * finalizer name.
+ *
+ * @param context of reconciler
+ * @return updated resource from the server response
+ * @param primary resource type
+ * @see #addFinalizer(Context, String)
+ */
+ public static
P addFinalizer(Context
context) {
+ return addFinalizer(context, context.getControllerConfiguration().getFinalizerName());
+ }
+
+ /**
+ * Adds finalizer to the resource using JSON Patch. Retries conflicts and unprocessable content
+ * (HTTP 422), see {@link PrimaryUpdateAndCacheUtils#conflictRetryingPatch(KubernetesClient,
+ * HasMetadata, UnaryOperator, Predicate)} for details on retry. It does not try to add finalizer
+ * if there is already a finalizer or resource is marked for deletion.
+ *
+ * @return updated resource from the server response
+ */
+ public static
P addFinalizer(Context
context, String finalizerName) {
+ var resource = context.getPrimaryResource();
+ if (resource.isMarkedForDeletion() || resource.hasFinalizer(finalizerName)) {
+ return resource;
+ }
+ return conflictRetryingPatch(
+ context,
+ r -> {
+ r.addFinalizer(finalizerName);
+ return r;
+ },
+ r -> !r.hasFinalizer(finalizerName));
+ }
+
+ /**
+ * Removes the default finalizer (from controller configuration) from the primary resource. This
+ * is a convenience method that calls {@link #removeFinalizer(Context, String)} with the
+ * configured finalizer name.
+ *
+ * @param context of reconciler
+ * @return updated resource from the server response
+ * @param
primary resource type
+ * @see #removeFinalizer(Context, String)
+ */
+ public static
P removeFinalizer(Context
context) {
+ return removeFinalizer(context, context.getControllerConfiguration().getFinalizerName());
+ }
+
+ /**
+ * Removes the target finalizer from target resource. Uses JSON Patch and handles retries, see
+ * {@link PrimaryUpdateAndCacheUtils#conflictRetryingPatch(KubernetesClient, HasMetadata,
+ * UnaryOperator, Predicate)} for details. It does not try to remove finalizer if finalizer is not
+ * present on the resource.
+ *
+ * @return updated resource from the server response
+ */
+ public static
P removeFinalizer(
+ Context
context, String finalizerName) {
+ var resource = context.getPrimaryResource();
+ if (!resource.hasFinalizer(finalizerName)) {
+ return resource;
+ }
+ return conflictRetryingPatch(
+ context,
+ r -> {
+ r.removeFinalizer(finalizerName);
+ return r;
+ },
+ r -> r.hasFinalizer(finalizerName));
+ }
+
+ /**
+ * Patches the resource using JSON Patch. In case the server responds with conflict (HTTP 409) or
+ * unprocessable content (HTTP 422) it retries the operation up to the maximum number defined in
+ * {@link PrimaryUpdateAndCacheUtils#DEFAULT_MAX_RETRY}.
+ *
+ * @param context reconiciliation context
+ * @param resourceChangesOperator changes to be done on the resource before update
+ * @param preCondition condition to check if the patch operation still needs to be performed or
+ * not.
+ * @return updated resource from the server or unchanged if the precondition does not hold.
+ * @param
resource type
+ */
+ @SuppressWarnings("unchecked")
+ private static
P conflictRetryingPatch(
+ Context
context, UnaryOperator
resourceChangesOperator, Predicate
preCondition) {
+ var resource = context.getPrimaryResource();
+ var client = context.getClient();
+ if (log.isDebugEnabled()) {
+ log.debug("Conflict retrying update for: {}", ResourceID.fromResource(resource));
+ }
+ int retryIndex = 0;
+ while (true) {
+ try {
+ if (!preCondition.test(resource)) {
+ return resource;
+ }
+ return jsonPatchPrimary(context, resource, resourceChangesOperator, false);
+ } 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 = client.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 the default finalizer (from controller configuration) to the primary resource using
+ * Server-Side Apply. This is a convenience method that calls {@link #addFinalizerWithSSA(Context,
+ * String)} with the configured finalizer name.
+ *
+ * @param context of reconciler
+ * @return the patched resource from the server response
+ * @param
primary resource type
+ * @see #addFinalizerWithSSA(Context, String)
+ */
+ public static
P addFinalizerWithSSA(Context
context) {
+ return addFinalizerWithSSA(context, context.getControllerConfiguration().getFinalizerName());
+ }
+
+ /**
+ * Adds finalizer using Server-Side Apply. In the background this method creates a fresh copy of
+ * the target resource, setting only name, namespace and finalizer. Does not use optimistic
+ * locking for the patch.
+ *
+ * @param context of reconciler
+ * @param finalizerName name of the finalizer to add
+ * @return the patched resource from the server response
+ * @param
primary resource type
+ */
+ public static
P addFinalizerWithSSA(
+ Context
context, String finalizerName) {
+ var originalResource = context.getPrimaryResource();
+ if (log.isDebugEnabled()) {
+ 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 serverSideApplyPrimary(context, resource, false);
+ } 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);
+ }
+ }
+
+ /**
+ * Compares resource versions of two resources. This is a convenience method that extracts the
+ * resource versions from the metadata and delegates to {@link #compareResourceVersions(String,
+ * String)}.
+ *
+ * @param h1 first resource
+ * @param h2 second resource
+ * @return negative if h1 is older, zero if equal, positive if h1 is newer
+ * @throws NonComparableResourceVersionException if either resource version is invalid
+ */
+ public static int compareResourceVersions(HasMetadata h1, HasMetadata h2) {
+ return compareResourceVersions(
+ h1.getMetadata().getResourceVersion(), h2.getMetadata().getResourceVersion());
+ }
+
+ /**
+ * Compares two Kubernetes resource versions numerically. Kubernetes resource versions are
+ * expected to be numeric strings that increase monotonically. This method assumes both versions
+ * are valid numeric strings without leading zeros.
+ *
+ * @param v1 first resource version
+ * @param v2 second resource version
+ * @return negative if v1 is older, zero if equal, positive if v1 is newer
+ * @throws NonComparableResourceVersionException if either resource version is empty, has leading
+ * zeros, or contains non-numeric characters
+ */
+ public static int compareResourceVersions(String v1, String v2) {
+ int v1Length = validateResourceVersion(v1);
+ int v2Length = validateResourceVersion(v2);
+ int comparison = v1Length - v2Length;
+ if (comparison != 0) {
+ return comparison;
+ }
+ for (int i = 0; i < v2Length; i++) {
+ int comp = v1.charAt(i) - v2.charAt(i);
+ if (comp != 0) {
+ return comp;
+ }
+ }
+ return 0;
+ }
+
+ private static int validateResourceVersion(String v1) {
+ int v1Length = v1.length();
+ if (v1Length == 0) {
+ throw new NonComparableResourceVersionException("Resource version is empty");
+ }
+ for (int i = 0; i < v1Length; i++) {
+ char char1 = v1.charAt(i);
+ if (char1 == '0') {
+ if (i == 0) {
+ throw new NonComparableResourceVersionException(
+ "Resource version cannot begin with 0: " + v1);
+ }
+ } else if (char1 < '0' || char1 > '9') {
+ throw new NonComparableResourceVersionException(
+ "Non numeric characters in resource version: " + v1);
+ }
+ }
+ return v1Length;
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java
new file mode 100644
index 0000000000..3e1a4f9b14
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright Java Operator SDK Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java
index a7c5ce9e2d..8dc62b4ca7 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java
@@ -23,6 +23,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
@@ -85,7 +86,7 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context c
if (creatable() || updatable()) {
if (actualResource == null) {
if (creatable) {
- var desired = desired(primary, context);
+ var desired = getOrComputeDesired(context);
throwIfNull(desired, primary, "Desired");
logForOperation("Creating", primary, desired);
var createdResource = handleCreate(desired, primary, context);
@@ -95,7 +96,8 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context c
if (updatable()) {
final Matcher.Result match = match(actualResource, primary, context);
if (!match.matched()) {
- final var desired = match.computedDesired().orElseGet(() -> desired(primary, context));
+ final var desired =
+ match.computedDesired().orElseGet(() -> getOrComputeDesired(context));
throwIfNull(desired, primary, "Desired");
logForOperation("Updating", primary, desired);
var updatedResource = handleUpdate(actualResource, desired, primary, context);
@@ -127,7 +129,6 @@ protected ReconcileResult reconcile(P primary, R actualResource, Context c
@Override
public Optional getSecondaryResource(P primary, Context context) {
-
var secondaryResources = context.getSecondaryResources(resourceType());
if (secondaryResources.isEmpty()) {
return Optional.empty();
@@ -212,6 +213,27 @@ protected R desired(P primary, Context
context) {
+ " updated");
}
+ /**
+ * Retrieves the desired state from the {@link Context} if it has already been computed or calls
+ * {@link #desired(HasMetadata, Context)} and stores its result in the context for further use.
+ * This ensures that {@code desired} is only called once per reconciliation to avoid unneeded
+ * processing and supports scenarios where idempotent computation of the desired state is not
+ * feasible.
+ *
+ *
Note that this method should normally only be called by the SDK itself and exclusively (i.e.
+ * {@link #desired(HasMetadata, Context)} should not be called directly by the SDK) whenever the
+ * desired state is needed to ensure it is properly cached for the current reconciliation.
+ *
+ * @param context the {@link Context} in scope for the current reconciliation
+ * @return the desired state associated with this dependent resource based on the currently
+ * in-scope primary resource as found in the context
+ */
+ protected R getOrComputeDesired(Context
context) {
+ assert context instanceof DefaultContext
;
+ DefaultContext
defaultContext = (DefaultContext
) context;
+ return defaultContext.getOrComputeDesiredStateFor(this, p -> desired(p, defaultContext));
+ }
+
public void delete(P primary, Context
context) {
dependentResourceReconciler.delete(primary, context);
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java
index e601e937cf..7b83a377c1 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java
@@ -105,7 +105,7 @@ protected void handleExplicitStateCreation(P primary, R created, Context
cont
@Override
public Matcher.Result match(R resource, P primary, Context context) {
- var desired = desired(primary, context);
+ var desired = getOrComputeDesired(context);
return Matcher.Result.computed(resource.equals(desired), desired);
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java
index 5b3617c26c..23135f81b1 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java
@@ -27,7 +27,6 @@
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
-import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
class BulkDependentResourceReconciler
implements DependentResourceReconciler {
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java
index 0ba48797af..5562c883e2 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java
@@ -138,7 +138,7 @@ public static Matcher.Result m
Context context,
boolean labelsAndAnnotationsEquality,
String... ignorePaths) {
- final var desired = dependentResource.desired(primary, context);
+ final var desired = dependentResource.getOrComputeDesired(context);
return match(desired, actualResource, labelsAndAnnotationsEquality, context, ignorePaths);
}
@@ -150,7 +150,7 @@ public static Matcher.Result m
boolean specEquality,
boolean labelsAndAnnotationsEquality,
String... ignorePaths) {
- final var desired = dependentResource.desired(primary, context);
+ final var desired = dependentResource.getOrComputeDesired(context);
return match(
desired, actualResource, labelsAndAnnotationsEquality, specEquality, context, ignorePaths);
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
index 05cddcade1..b9ea27b190 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
@@ -55,7 +55,6 @@ public abstract class KubernetesDependentResource kubernetesDependentResourceConfig;
private volatile Boolean useSSA;
- private volatile Boolean usePreviousAnnotationForEventFiltering;
public KubernetesDependentResource() {}
@@ -72,6 +71,25 @@ public void configureWith(KubernetesDependentResourceConfig config) {
this.kubernetesDependentResourceConfig = config;
}
+ @Override
+ protected R handleCreate(R desired, P primary, Context context) {
+ return eventSource()
+ .orElseThrow()
+ .eventFilteringUpdateAndCacheResource(
+ desired,
+ toCreate -> KubernetesDependentResource.super.handleCreate(toCreate, primary, context));
+ }
+
+ @Override
+ protected R handleUpdate(R actual, R desired, P primary, Context
context) {
+ return eventSource()
+ .orElseThrow()
+ .eventFilteringUpdateAndCacheResource(
+ desired,
+ toUpdate ->
+ KubernetesDependentResource.super.handleUpdate(actual, toUpdate, primary, context));
+ }
+
@SuppressWarnings("unused")
public R create(R desired, P primary, Context
context) {
if (useSSA(context)) {
@@ -123,7 +141,7 @@ public R update(R actual, R desired, P primary, Context
context) {
@Override
public Result match(R actualResource, P primary, Context context) {
- final var desired = desired(primary, context);
+ final var desired = getOrComputeDesired(context);
return match(actualResource, desired, primary, context);
}
@@ -158,14 +176,6 @@ protected void addMetadata(
} else {
annotations.remove(InformerEventSource.PREVIOUS_ANNOTATION_KEY);
}
- } else if (usePreviousAnnotation(context)) { // set a new one
- eventSource()
- .orElseThrow()
- .addPreviousAnnotation(
- Optional.ofNullable(actualResource)
- .map(r -> r.getMetadata().getResourceVersion())
- .orElse(null),
- target);
}
addReferenceHandlingMetadata(target, primary);
}
@@ -181,22 +191,6 @@ protected boolean useSSA(Context
context) {
return useSSA;
}
- private boolean usePreviousAnnotation(Context
context) {
- if (usePreviousAnnotationForEventFiltering == null) {
- usePreviousAnnotationForEventFiltering =
- context
- .getControllerConfiguration()
- .getConfigurationService()
- .previousAnnotationForDependentResourcesEventFiltering()
- && !context
- .getControllerConfiguration()
- .getConfigurationService()
- .withPreviousAnnotationForDependentResourcesBlocklist()
- .contains(this.resourceType());
- }
- return usePreviousAnnotationForEventFiltering;
- }
-
@Override
protected void handleDelete(P primary, R secondary, Context
context) {
if (secondary != null) {
@@ -301,7 +295,7 @@ protected Optional selectTargetSecondaryResource(
* @return id of the target managed resource
*/
protected ResourceID targetSecondaryResourceID(P primary, Context context) {
- return ResourceID.fromResource(desired(primary, context));
+ return ResourceID.fromResource(getOrComputeDesired(context));
}
protected boolean addOwnerReference() {
@@ -309,8 +303,8 @@ protected boolean addOwnerReference() {
}
@Override
- protected R desired(P primary, Context
context) {
- return super.desired(primary, context);
+ protected R getOrComputeDesired(Context
context) {
+ return super.getOrComputeDesired(context);
}
@Override
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
index 8a1ddb4c5a..a9e542ee8c 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
@@ -15,9 +15,7 @@
*/
package io.javaoperatorsdk.operator.processing.event;
-import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
-import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,20 +24,18 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.api.model.Namespaced;
-import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
-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.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.Cloner;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.BaseControl;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
+import io.javaoperatorsdk.operator.api.reconciler.ReconcileUtils;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.processing.Controller;
@@ -49,8 +45,6 @@
/** Handles calls and results of a Reconciler and finalizer related logic */
class ReconciliationDispatcher
{
- public static final int MAX_UPDATE_RETRY = 10;
-
private static final Logger log = LoggerFactory.getLogger(ReconciliationDispatcher.class);
private final Controller
controller;
@@ -119,7 +113,7 @@ && shouldNotDispatchToCleanupWhenMarkedForDeletion(originalResource)) {
// checking the cleaner for all-event-mode
if (!triggerOnAllEvent() && markedForDeletion) {
- return handleCleanup(resourceForExecution, originalResource, context, executionScope);
+ return handleCleanup(resourceForExecution, context, executionScope);
} else {
return handleReconcile(executionScope, resourceForExecution, originalResource, context);
}
@@ -148,9 +142,9 @@ private PostExecutionControl
handleReconcile(
*/
P updatedResource;
if (useSSA) {
- updatedResource = addFinalizerWithSSA(originalResource);
+ updatedResource = ReconcileUtils.addFinalizerWithSSA(context);
} else {
- updatedResource = updateCustomResourceWithFinalizer(resourceForExecution, originalResource);
+ updatedResource = ReconcileUtils.addFinalizer(context);
}
return PostExecutionControl.onlyFinalizerAdded(updatedResource);
} else {
@@ -194,7 +188,7 @@ private PostExecutionControl
reconcileExecution(
}
if (updateControl.isPatchResource()) {
- updatedCustomResource = patchResource(toUpdate, originalResource);
+ updatedCustomResource = patchResource(context, toUpdate, originalResource);
if (!useSSA) {
toUpdate
.getMetadata()
@@ -203,7 +197,7 @@ private PostExecutionControl
reconcileExecution(
}
if (updateControl.isPatchStatus()) {
- customResourceFacade.patchStatus(toUpdate, originalResource);
+ customResourceFacade.patchStatus(context, toUpdate, originalResource);
}
return createPostExecutionControl(updatedCustomResource, updateControl, executionScope);
}
@@ -241,7 +235,7 @@ public boolean isLastAttempt() {
try {
updatedResource =
customResourceFacade.patchStatus(
- errorStatusUpdateControl.getResource().orElseThrow(), originalResource);
+ context, errorStatusUpdateControl.getResource().orElseThrow(), originalResource);
} catch (Exception ex) {
int code = ex instanceof KubernetesClientException kcex ? kcex.getCode() : -1;
Level exceptionLevel = Level.ERROR;
@@ -317,10 +311,7 @@ private void updatePostExecutionControlWithReschedule(
}
private PostExecutionControl
handleCleanup(
- P resourceForExecution,
- P originalResource,
- Context
context,
- ExecutionScope
executionScope) {
+ P resourceForExecution, Context
context, ExecutionScope
executionScope) {
if (log.isDebugEnabled()) {
log.debug(
"Executing delete for resource: {} with version: {}",
@@ -334,24 +325,7 @@ private PostExecutionControl
handleCleanup(
// cleanup is finished, nothing left to be done
final var finalizerName = configuration().getFinalizerName();
if (deleteControl.isRemoveFinalizer() && resourceForExecution.hasFinalizer(finalizerName)) {
- P customResource =
- conflictRetryingPatch(
- resourceForExecution,
- originalResource,
- r -> {
- // the operator might not be allowed to retrieve the resource on a retry, e.g.
- // when its
- // permissions are removed by deleting the namespace concurrently
- if (r == null) {
- log.warn(
- "Could not remove finalizer on null resource: {} with version: {}",
- getUID(resourceForExecution),
- getVersion(resourceForExecution));
- return false;
- }
- return r.removeFinalizer(finalizerName);
- },
- true);
+ P customResource = ReconcileUtils.removeFinalizer(context);
return PostExecutionControl.customResourceFinalizerRemoved(customResource);
}
}
@@ -367,45 +341,7 @@ private PostExecutionControl
handleCleanup(
return postExecutionControl;
}
- @SuppressWarnings("unchecked")
- private P addFinalizerWithSSA(P originalResource) {
- 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(configuration().getFinalizerName());
- return customResourceFacade.patchResourceWithSSA(resource);
- } 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);
- }
- }
-
- private P updateCustomResourceWithFinalizer(P resourceForExecution, P originalResource) {
- log.debug(
- "Adding finalizer for resource: {} version: {}",
- getUID(originalResource),
- getVersion(originalResource));
- return conflictRetryingPatch(
- resourceForExecution,
- originalResource,
- r -> r.addFinalizer(configuration().getFinalizerName()),
- false);
- }
-
- private P patchResource(P resource, P originalResource) {
+ private P patchResource(Context
context, P resource, P originalResource) {
log.debug(
"Updating resource: {} with version: {}; SSA: {}",
getUID(resource),
@@ -418,64 +354,13 @@ private P patchResource(P resource, P originalResource) {
// addFinalizer already prevents adding an already present finalizer so no need to check
resource.addFinalizer(finalizerName);
}
- return customResourceFacade.patchResource(resource, originalResource);
+ return customResourceFacade.patchResource(context, resource, originalResource);
}
ControllerConfiguration
configuration() {
return controller.getConfiguration();
}
- public P conflictRetryingPatch(
- P resource,
- P originalResource,
- Function
modificationFunction,
- boolean forceNotUseSSA) {
- if (log.isDebugEnabled()) {
- log.debug("Conflict retrying update for: {}", ResourceID.fromResource(resource));
- }
- int retryIndex = 0;
- while (true) {
- try {
- var modified = modificationFunction.apply(resource);
- if (Boolean.FALSE.equals(modified)) {
- return resource;
- }
- if (forceNotUseSSA) {
- return customResourceFacade.patchResourceWithoutSSA(resource, originalResource);
- } else {
- return customResourceFacade.patchResource(resource, originalResource);
- }
- } 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 >= MAX_UPDATE_RETRY) {
- throw new OperatorException(
- "Exceeded maximum ("
- + MAX_UPDATE_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());
- resource =
- customResourceFacade.getResource(
- resource.getMetadata().getNamespace(), resource.getMetadata().getName());
- }
- }
- }
-
private void validateExecutionScope(ExecutionScope
executionScope) {
if (!triggerOnAllEvent()
&& (executionScope.isDeleteEvent() || executionScope.isDeleteFinalStateUnknown())) {
@@ -490,7 +375,6 @@ static class CustomResourceFacade {
private final MixedOperation, Resource> resourceOperation;
private final boolean useSSA;
- private final String fieldManager;
private final Cloner cloner;
public CustomResourceFacade(
@@ -499,7 +383,6 @@ public CustomResourceFacade(
Cloner cloner) {
this.resourceOperation = resourceOperation;
this.useSSA = configuration.getConfigurationService().useSSAToPatchPrimaryResource();
- this.fieldManager = configuration.fieldManager();
this.cloner = cloner;
}
@@ -515,7 +398,7 @@ public R patchResourceWithoutSSA(R resource, R originalResource) {
return resource(originalResource).edit(r -> resource);
}
- public R patchResource(R resource, R originalResource) {
+ public R patchResource(Context context, R resource, R originalResource) {
if (log.isDebugEnabled()) {
log.debug(
"Trying to replace resource {}, version: {}",
@@ -523,35 +406,28 @@ public R patchResource(R resource, R originalResource) {
resource.getMetadata().getResourceVersion());
}
if (useSSA) {
- return patchResourceWithSSA(resource);
+ return ReconcileUtils.serverSideApplyPrimary(context, resource);
} else {
- return resource(originalResource).edit(r -> resource);
+ return ReconcileUtils.jsonPatchPrimary(context, originalResource, r -> resource);
}
}
- public R patchStatus(R resource, R originalResource) {
+ public R patchStatus(Context context, R resource, R originalResource) {
log.trace("Patching status for resource: {} with ssa: {}", resource, useSSA);
if (useSSA) {
var managedFields = resource.getMetadata().getManagedFields();
try {
resource.getMetadata().setManagedFields(null);
- var res = resource(resource);
- return res.subresource("status")
- .patch(
- new PatchContext.Builder()
- .withFieldManager(fieldManager)
- .withForce(true)
- .withPatchType(PatchType.SERVER_SIDE_APPLY)
- .build());
+ return ReconcileUtils.serverSideApplyPrimaryStatus(context, resource);
} finally {
resource.getMetadata().setManagedFields(managedFields);
}
} else {
- return editStatus(resource, originalResource);
+ return editStatus(context, resource, originalResource);
}
}
- private R editStatus(R resource, R originalResource) {
+ private R editStatus(Context context, R resource, R originalResource) {
String resourceVersion = resource.getMetadata().getResourceVersion();
// the cached resource should not be changed in any circumstances
// that can lead to all kinds of race conditions.
@@ -559,10 +435,11 @@ private R editStatus(R resource, R originalResource) {
try {
clonedOriginal.getMetadata().setResourceVersion(null);
resource.getMetadata().setResourceVersion(null);
- var res = resource(clonedOriginal);
- return res.editStatus(
+ return ReconcileUtils.jsonPatchPrimaryStatus(
+ context,
+ clonedOriginal,
r -> {
- ReconcilerUtils.setStatus(r, ReconcilerUtils.getStatus(resource));
+ ReconcilerUtilsInternal.setStatus(r, ReconcilerUtilsInternal.getStatus(resource));
return r;
});
} finally {
@@ -572,16 +449,6 @@ private R editStatus(R resource, R originalResource) {
}
}
- public R patchResourceWithSSA(R resource) {
- return resource(resource)
- .patch(
- new PatchContext.Builder()
- .withFieldManager(fieldManager)
- .withForce(true)
- .withPatchType(PatchType.SERVER_SIDE_APPLY)
- .build());
- }
-
private Resource resource(R resource) {
return resource instanceof Namespaced
? resourceOperation.inNamespace(resource.getMetadata().getNamespace()).resource(resource)
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/CacheKeyMapper.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/CacheKeyMapper.java
new file mode 100644
index 0000000000..3e1a4f9b14
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/CacheKeyMapper.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright Java Operator SDK Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java
index b7a6406e20..3c232ceb07 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java
@@ -32,7 +32,7 @@
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.handleKubernetesClientException;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.handleKubernetesClientException;
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;
import static io.javaoperatorsdk.operator.processing.event.source.controller.InternalEventFilters.*;
@@ -47,7 +47,11 @@ public class ControllerEventSource
@SuppressWarnings({"unchecked", "rawtypes"})
public ControllerEventSource(Controller controller) {
- super(NAME, controller.getCRClient(), controller.getConfiguration(), false);
+ super(
+ NAME,
+ controller.getCRClient(),
+ controller.getConfiguration(),
+ controller.getConfiguration().getInformerConfig().isComparableResourceVersions());
this.controller = controller;
final var config = controller.getConfiguration();
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java
index ec11db25f4..8f14f4961e 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java
@@ -17,7 +17,6 @@
import java.util.Optional;
import java.util.Set;
-import java.util.UUID;
import java.util.stream.Collectors;
import org.slf4j.Logger;
@@ -28,43 +27,19 @@
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration;
-import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.EventHandler;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper;
+import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_COMPARABLE_RESOURCE_VERSION;
+
/**
* Wraps informer(s) so they are connected to the eventing system of the framework. Note that since
* this is built on top of Fabric8 client Informers, it also supports caching resources using
- * caching from informer caches as well as additional caches described below.
- *
- * InformerEventSource also supports two features to better handle events and caching of
- * resources on top of Informers from the Fabric8 Kubernetes client. These two features are related
- * to each other as follows:
- *
- *
- * - Ensuring the cache contains the fresh resource after an update. This is important for
- * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} and mainly
- * for {@link
- * io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource} so
- * that {@link
- * io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource#getSecondaryResource(HasMetadata,
- * Context)} always returns the latest version of the resource after a reconciliation. To
- * achieve this {@link #handleRecentResourceUpdate(ResourceID, HasMetadata, HasMetadata)} and
- * {@link #handleRecentResourceCreate(ResourceID, HasMetadata)} need to be called explicitly
- * after a resource is created or updated using the kubernetes client. These calls are done
- * automatically by the KubernetesDependentResource implementation. In the background this
- * will store the new resource in a temporary cache {@link TemporaryResourceCache} which does
- * additional checks. After a new event is received the cached object is removed from this
- * cache, since it is then usually already in the informer cache.
- *
- Avoiding unneeded reconciliations after resources are created or updated. This filters out
- * events that are the results of updates and creates made by the controller itself because we
- * typically don't want the associated informer to trigger an event causing a useless
- * reconciliation (as the change originates from the reconciler itself). For the details see
- * {@link #canSkipEvent(HasMetadata, HasMetadata, ResourceID)} and related usage.
- *
+ * caching from informer caches as well as filtering events which are result of the controller's
+ * update.
*
* @param resource type being watched
* @param type of the associated primary resource
@@ -78,28 +53,24 @@ public class InformerEventSource
// we need direct control for the indexer to propagate the just update resource also to the index
private final PrimaryToSecondaryIndex primaryToSecondaryIndex;
private final PrimaryToSecondaryMapper primaryToSecondaryMapper;
- private final String id = UUID.randomUUID().toString();
public InformerEventSource(
InformerEventSourceConfiguration configuration, EventSourceContext context) {
this(
configuration,
configuration.getKubernetesClient().orElse(context.getClient()),
- context
- .getControllerConfiguration()
- .getConfigurationService()
- .parseResourceVersionsForEventFilteringAndCaching());
+ configuration.comparableResourceVersion());
}
InformerEventSource(InformerEventSourceConfiguration configuration, KubernetesClient client) {
- this(configuration, client, false);
+ this(configuration, client, DEFAULT_COMPARABLE_RESOURCE_VERSION);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private InformerEventSource(
InformerEventSourceConfiguration configuration,
KubernetesClient client,
- boolean parseResourceVersions) {
+ boolean comparableResourceVersions) {
super(
configuration.name(),
configuration
@@ -107,7 +78,7 @@ private InformerEventSource(
.map(gvk -> client.genericKubernetesResources(gvk.apiVersion(), gvk.getKind()))
.orElseGet(() -> (MixedOperation) client.resources(configuration.getResourceClass())),
configuration,
- parseResourceVersions);
+ comparableResourceVersions);
// If there is a primary to secondary mapper there is no need for primary to secondary index.
primaryToSecondaryMapper = configuration.getPrimaryToSecondaryMapper();
if (useSecondaryToPrimaryIndex()) {
@@ -134,9 +105,7 @@ public void onAdd(R newResource) {
resourceType().getSimpleName(),
newResource.getMetadata().getResourceVersion());
}
- primaryToSecondaryIndex.onAddOrUpdate(newResource);
- onAddOrUpdate(
- Operation.ADD, newResource, null, () -> InformerEventSource.super.onAdd(newResource));
+ onAddOrUpdate(Operation.ADD, newResource, null);
}
@Override
@@ -149,16 +118,11 @@ public void onUpdate(R oldObject, R newObject) {
newObject.getMetadata().getResourceVersion(),
oldObject.getMetadata().getResourceVersion());
}
- primaryToSecondaryIndex.onAddOrUpdate(newObject);
- onAddOrUpdate(
- Operation.UPDATE,
- newObject,
- oldObject,
- () -> InformerEventSource.super.onUpdate(oldObject, newObject));
+ onAddOrUpdate(Operation.UPDATE, newObject, oldObject);
}
@Override
- public void onDelete(R resource, boolean b) {
+ public synchronized void onDelete(R resource, boolean b) {
if (log.isDebugEnabled()) {
log.debug(
"On delete event received for resource id: {} type: {}",
@@ -180,68 +144,28 @@ public synchronized void start() {
manager().list().forEach(primaryToSecondaryIndex::onAddOrUpdate);
}
- private synchronized void onAddOrUpdate(
- Operation operation, R newObject, R oldObject, Runnable superOnOp) {
+ private synchronized void onAddOrUpdate(Operation operation, R newObject, R oldObject) {
+ primaryToSecondaryIndex.onAddOrUpdate(newObject);
var resourceID = ResourceID.fromResource(newObject);
- if (canSkipEvent(newObject, oldObject, resourceID)) {
+ if (temporaryResourceCache.onAddOrUpdateEvent(newObject)) {
log.debug(
"Skipping event propagation for {}, since was a result of a reconcile action. Resource"
+ " ID: {}",
operation,
ResourceID.fromResource(newObject));
- superOnOp.run();
+ } else if (eventAcceptedByFilter(operation, newObject, oldObject)) {
+ log.debug(
+ "Propagating event for {}, resource with same version not result of a reconciliation."
+ + " Resource ID: {}",
+ operation,
+ resourceID);
+ propagateEvent(newObject);
} else {
- superOnOp.run();
- if (eventAcceptedByFilter(operation, newObject, oldObject)) {
- log.debug(
- "Propagating event for {}, resource with same version not result of a reconciliation."
- + " Resource ID: {}",
- operation,
- resourceID);
- propagateEvent(newObject);
- } else {
- log.debug("Event filtered out for operation: {}, resourceID: {}", operation, resourceID);
- }
+ log.debug("Event filtered out for operation: {}, resourceID: {}", operation, resourceID);
}
}
- private boolean canSkipEvent(R newObject, R oldObject, ResourceID resourceID) {
- var res = temporaryResourceCache.getResourceFromCache(resourceID);
- if (res.isEmpty()) {
- return isEventKnownFromAnnotation(newObject, oldObject);
- }
- boolean resVersionsEqual =
- newObject
- .getMetadata()
- .getResourceVersion()
- .equals(res.get().getMetadata().getResourceVersion());
- log.debug(
- "Resource found in temporal cache for id: {} resource versions equal: {}",
- resourceID,
- resVersionsEqual);
- return resVersionsEqual
- || temporaryResourceCache.isLaterResourceVersion(resourceID, res.get(), newObject);
- }
-
- private boolean isEventKnownFromAnnotation(R newObject, R oldObject) {
- String previous = newObject.getMetadata().getAnnotations().get(PREVIOUS_ANNOTATION_KEY);
- boolean known = false;
- if (previous != null) {
- String[] parts = previous.split(",");
- if (id.equals(parts[0])) {
- if (oldObject == null && parts.length == 1) {
- known = true;
- } else if (oldObject != null
- && parts.length == 2
- && oldObject.getMetadata().getResourceVersion().equals(parts[1])) {
- known = true;
- }
- }
- }
- return known;
- }
-
private void propagateEvent(R object) {
var primaryResourceIdSet =
configuration().getSecondaryToPrimaryMapper().toPrimaryResourceIDs(object);
@@ -289,23 +213,19 @@ public Set getSecondaryResources(P primary) {
}
@Override
- public synchronized void handleRecentResourceUpdate(
+ public void handleRecentResourceUpdate(
ResourceID resourceID, R resource, R previousVersionOfResource) {
- handleRecentCreateOrUpdate(Operation.UPDATE, resource, previousVersionOfResource);
+ handleRecentCreateOrUpdate(resource);
}
@Override
- public synchronized void handleRecentResourceCreate(ResourceID resourceID, R resource) {
- handleRecentCreateOrUpdate(Operation.ADD, resource, null);
+ public void handleRecentResourceCreate(ResourceID resourceID, R resource) {
+ handleRecentCreateOrUpdate(resource);
}
- private void handleRecentCreateOrUpdate(Operation operation, R newResource, R oldResource) {
+ private void handleRecentCreateOrUpdate(R newResource) {
primaryToSecondaryIndex.onAddOrUpdate(newResource);
- temporaryResourceCache.putResource(
- newResource,
- Optional.ofNullable(oldResource)
- .map(r -> r.getMetadata().getResourceVersion())
- .orElse(null));
+ temporaryResourceCache.putResource(newResource);
}
private boolean useSecondaryToPrimaryIndex() {
@@ -333,22 +253,6 @@ private boolean acceptedByDeleteFilters(R resource, boolean b) {
&& (genericFilter == null || genericFilter.accept(resource));
}
- /**
- * Add an annotation to the resource so that the subsequent will be omitted
- *
- * @param resourceVersion null if there is no prior version
- * @param target mutable resource that will be returned
- */
- public R addPreviousAnnotation(String resourceVersion, R target) {
- target
- .getMetadata()
- .getAnnotations()
- .put(
- PREVIOUS_ANNOTATION_KEY,
- id + Optional.ofNullable(resourceVersion).map(rv -> "," + rv).orElse(""));
- return target;
- }
-
private enum Operation {
ADD,
UPDATE
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java
index abd2b6a752..42e06c9d9a 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java
@@ -32,7 +32,7 @@
import io.fabric8.kubernetes.client.dsl.Resource;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.Informable;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
@@ -253,7 +253,7 @@ public String toString() {
final var informerConfig = configuration.getInformerConfig();
final var selector = informerConfig.getLabelSelector();
return "InformerManager ["
- + ReconcilerUtils.getResourceTypeNameWithVersion(configuration.getResourceClass())
+ + ReconcilerUtilsInternal.getResourceTypeNameWithVersion(configuration.getResourceClass())
+ "] watching: "
+ informerConfig.getEffectiveNamespaces(controllerConfiguration)
+ (selector != null ? " selector: " + selector : "");
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java
index 2a6c7ef206..60497bc0c9 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java
@@ -35,7 +35,7 @@
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
import io.fabric8.kubernetes.client.informers.cache.Cache;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.health.InformerHealthIndicator;
import io.javaoperatorsdk.operator.health.Status;
@@ -131,7 +131,7 @@ public void start() throws OperatorException {
}
} catch (Exception e) {
- ReconcilerUtils.handleKubernetesClientException(
+ ReconcilerUtilsInternal.handleKubernetesClientException(
e, HasMetadata.getFullResourceName(informer.getApiTypeClass()));
throw new OperatorException(
"Couldn't start informer for " + versionedFullResourceName() + " resources", e);
@@ -143,7 +143,7 @@ private String versionedFullResourceName() {
if (apiTypeClass.isAssignableFrom(GenericKubernetesResource.class)) {
return GenericKubernetesResource.class.getSimpleName();
}
- return ReconcilerUtils.getResourceTypeNameWithVersion(apiTypeClass);
+ return ReconcilerUtilsInternal.getResourceTypeNameWithVersion(apiTypeClass);
}
@Override
@@ -156,6 +156,10 @@ public Optional get(ResourceID resourceID) {
return Optional.ofNullable(cache.getByKey(getKey(resourceID)));
}
+ public String getLastSyncResourceVersion() {
+ return this.informer.lastSyncResourceVersion();
+ }
+
private String getKey(ResourceID resourceID) {
return Cache.namespaceKeyFunc(resourceID.getNamespace().orElse(null), resourceID.getName());
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java
index 2679918b60..0ade9a44c0 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java
@@ -22,6 +22,7 @@
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import org.slf4j.Logger;
@@ -34,6 +35,7 @@
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.Informable;
import io.javaoperatorsdk.operator.api.config.NamespaceChangeable;
+import io.javaoperatorsdk.operator.api.reconciler.ReconcileUtils;
import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller;
import io.javaoperatorsdk.operator.health.InformerHealthIndicator;
import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator;
@@ -55,7 +57,7 @@ public abstract class ManagedInformerEventSource<
private static final Logger log = LoggerFactory.getLogger(ManagedInformerEventSource.class);
private InformerManager cache;
- private final boolean parseResourceVersions;
+ private final boolean comparableResourceVersions;
private ControllerConfiguration controllerConfiguration;
private final C configuration;
private final Map>> indexers = new HashMap<>();
@@ -63,9 +65,9 @@ public abstract class ManagedInformerEventSource<
protected MixedOperation client;
protected ManagedInformerEventSource(
- String name, MixedOperation client, C configuration, boolean parseResourceVersions) {
+ String name, MixedOperation client, C configuration, boolean comparableResourceVersions) {
super(configuration.getResourceClass(), name);
- this.parseResourceVersions = parseResourceVersions;
+ this.comparableResourceVersions = comparableResourceVersions;
this.client = client;
this.configuration = configuration;
}
@@ -96,13 +98,35 @@ public void changeNamespaces(Set namespaces) {
}
}
+ public R updateAndCacheResource(R resourceToUpdate, UnaryOperator updateMethod) {
+ ResourceID id = ResourceID.fromResource(resourceToUpdate);
+ var updated = updateMethod.apply(resourceToUpdate);
+ handleRecentResourceUpdate(id, updated, resourceToUpdate);
+ return updated;
+ }
+
+ public R eventFilteringUpdateAndCacheResource(R resourceToUpdate, UnaryOperator updateMethod) {
+ ResourceID id = ResourceID.fromResource(resourceToUpdate);
+ if (log.isDebugEnabled()) {
+ log.debug("Update and cache: {}", id);
+ }
+ try {
+ temporaryResourceCache.startModifying(id);
+ var updated = updateMethod.apply(resourceToUpdate);
+ handleRecentResourceUpdate(id, updated, resourceToUpdate);
+ return updated;
+ } finally {
+ temporaryResourceCache.doneModifying(id);
+ }
+ }
+
@SuppressWarnings("unchecked")
@Override
public synchronized void start() {
if (isRunning()) {
return;
}
- temporaryResourceCache = new TemporaryResourceCache<>(this, parseResourceVersions);
+ temporaryResourceCache = new TemporaryResourceCache<>(comparableResourceVersions);
this.cache = new InformerManager<>(client, configuration, this);
cache.setControllerConfiguration(controllerConfiguration);
cache.addIndexers(indexers);
@@ -122,30 +146,31 @@ public synchronized void stop() {
@Override
public void handleRecentResourceUpdate(
ResourceID resourceID, R resource, R previousVersionOfResource) {
- temporaryResourceCache.putResource(
- resource, previousVersionOfResource.getMetadata().getResourceVersion());
+ temporaryResourceCache.putResource(resource);
}
@Override
public void handleRecentResourceCreate(ResourceID resourceID, R resource) {
- temporaryResourceCache.putAddedResource(resource);
+ temporaryResourceCache.putResource(resource);
}
@Override
public Optional get(ResourceID resourceID) {
+ var res = cache.get(resourceID);
Optional resource = temporaryResourceCache.getResourceFromCache(resourceID);
- if (resource.isPresent()) {
- log.debug("Resource found in temporary cache for Resource ID: {}", resourceID);
+ if (comparableResourceVersions
+ && resource.isPresent()
+ && res.filter(r -> ReconcileUtils.compareResourceVersions(r, resource.orElseThrow()) > 0)
+ .isEmpty()) {
+ log.debug("Latest resource found in temporary cache for Resource ID: {}", resourceID);
return resource;
- } else {
- log.debug(
- "Resource not found in temporary cache reading it from informer cache,"
- + " for Resource ID: {}",
- resourceID);
- var res = cache.get(resourceID);
- log.debug("Resource found in cache: {} for id: {}", res.isPresent(), resourceID);
- return res;
}
+ log.debug(
+ "Resource not found, or older, in temporary cache. Found in informer cache {}, for"
+ + " Resource ID: {}",
+ res.isPresent(),
+ resourceID);
+ return res;
}
@SuppressWarnings("unused")
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java
index 06226ae4ba..d41706d702 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java
@@ -15,16 +15,16 @@
*/
package io.javaoperatorsdk.operator.processing.event.source.informer;
-import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.javaoperatorsdk.operator.api.config.ConfigurationService;
+import io.javaoperatorsdk.operator.api.reconciler.ReconcileUtils;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@@ -33,157 +33,152 @@
* a create or update is executed the subsequent getResource operation might not return the
* up-to-date resource from informer cache, since it is not received yet.
*
- * The idea of the solution is, that since an update (for create is simpler) was done
- * successfully, and optimistic locking is in place, there were no other operations between reading
- * the resource from the cache and the actual update. So when the new resource is stored in the
- * temporal cache only if the informer still has the previous resource version, from before the
- * update. If not, that means there were already updates on the cache (either by the actual update
- * from DependentResource or other) so the resource does not needs to be cached. Subsequently if
- * event received from the informer, it means that the cache of the informer was updated, so it
- * already contains a more fresh version of the resource.
+ *
Since an update (for create is simpler) was done successfully we can temporarily track that
+ * resource if its version is later than the events we've processed. We then know that we can skip
+ * all events that have the same resource version or earlier than the tracked resource. Once we
+ * process an event that has the same resource version or later, then we know the tracked resource
+ * can be removed.
+ *
+ *
In some cases it is possible for the informer to deliver events prior to the attempt to put
+ * the resource in the temporal cache. The startModifying/doneModifying methods are used to pause
+ * event delivery to ensure that temporal cache recognizes the put entry as an event that can be
+ * skipped.
+ *
+ *
If comparable resource versions are disabled, then this cache is effectively disabled.
*
* @param resource to cache.
*/
public class TemporaryResourceCache {
- static class ExpirationCache {
- private final LinkedHashMap cache;
- private final int ttlMs;
-
- public ExpirationCache(int maxEntries, int ttlMs) {
- this.ttlMs = ttlMs;
- this.cache =
- new LinkedHashMap<>() {
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- return size() > maxEntries;
- }
- };
- }
+ private static final Logger log = LoggerFactory.getLogger(TemporaryResourceCache.class);
- public void add(K key) {
- clean();
- cache.putIfAbsent(key, System.currentTimeMillis());
- }
+ private final Map cache = new ConcurrentHashMap<>();
+ private final boolean comparableResourceVersions;
+ private final Map activelyModifying = new ConcurrentHashMap<>();
+ private String latestResourceVersion;
- public boolean contains(K key) {
- clean();
- return cache.get(key) != null;
- }
+ public TemporaryResourceCache(boolean comparableResourceVersions) {
+ this.comparableResourceVersions = comparableResourceVersions;
+ }
- void clean() {
- if (!cache.isEmpty()) {
- long currentTimeMillis = System.currentTimeMillis();
- var iter = cache.entrySet().iterator();
- // the order will already be from oldest to newest, clean a fixed number of entries to
- // amortize the cost amongst multiple calls
- for (int i = 0; i < 10 && iter.hasNext(); i++) {
- var entry = iter.next();
- if (currentTimeMillis - entry.getValue() > ttlMs) {
- iter.remove();
- }
- }
- }
+ public void startModifying(ResourceID id) {
+ if (!comparableResourceVersions) {
+ return;
}
+ activelyModifying
+ .compute(
+ id,
+ (ignored, lock) -> {
+ if (lock != null) {
+ throw new IllegalStateException(); // concurrent modifications to the same resource
+ // not allowed - this could be relaxed if needed
+ }
+ return new ReentrantLock();
+ })
+ .lock();
}
- private static final Logger log = LoggerFactory.getLogger(TemporaryResourceCache.class);
-
- private final Map cache = new ConcurrentHashMap<>();
-
- // keep up to the last million deletions for up to 10 minutes
- private final ExpirationCache tombstones = new ExpirationCache<>(1000000, 1200000);
- private final ManagedInformerEventSource managedInformerEventSource;
- private final boolean parseResourceVersions;
-
- public TemporaryResourceCache(
- ManagedInformerEventSource managedInformerEventSource,
- boolean parseResourceVersions) {
- this.managedInformerEventSource = managedInformerEventSource;
- this.parseResourceVersions = parseResourceVersions;
+ public void doneModifying(ResourceID id) {
+ if (!comparableResourceVersions) {
+ return;
+ }
+ activelyModifying.computeIfPresent(
+ id,
+ (ignored, lock) -> {
+ lock.unlock();
+ return null;
+ });
}
- public synchronized void onDeleteEvent(T resource, boolean unknownState) {
- tombstones.add(resource.getMetadata().getUid());
+ public void onDeleteEvent(T resource, boolean unknownState) {
onEvent(resource, unknownState);
}
- public synchronized void onAddOrUpdateEvent(T resource) {
- onEvent(resource, false);
+ /**
+ * @return true if the resourceVersion was already known
+ */
+ public boolean onAddOrUpdateEvent(T resource) {
+ return onEvent(resource, false);
}
- synchronized void onEvent(T resource, boolean unknownState) {
- cache.computeIfPresent(
- ResourceID.fromResource(resource),
- (id, cached) ->
- (unknownState || !isLaterResourceVersion(id, cached, resource)) ? null : cached);
+ private boolean onEvent(T resource, boolean unknownState) {
+ ReentrantLock lock = activelyModifying.get(ResourceID.fromResource(resource));
+ if (lock != null) {
+ lock.lock(); // wait for the modification to finish
+ lock.unlock(); // simply unlock as the event is guaranteed after the modification
+ }
+ boolean[] known = new boolean[1];
+ synchronized (this) {
+ if (!unknownState) {
+ latestResourceVersion = resource.getMetadata().getResourceVersion();
+ }
+ cache.computeIfPresent(
+ ResourceID.fromResource(resource),
+ (id, cached) -> {
+ boolean remove = unknownState;
+ if (!unknownState) {
+ int comp = ReconcileUtils.compareResourceVersions(resource, cached);
+ if (comp >= 0) {
+ remove = true;
+ }
+ if (comp <= 0) {
+ known[0] = true;
+ }
+ }
+ if (remove) {
+ return null;
+ }
+ return cached;
+ });
+ return known[0];
+ }
}
- public synchronized void putAddedResource(T newResource) {
- putResource(newResource, null);
- }
+ /** put the item into the cache if it's for a later state than what has already been observed. */
+ public synchronized void putResource(T newResource) {
+ if (!comparableResourceVersions) {
+ return;
+ }
- /**
- * put the item into the cache if the previousResourceVersion matches the current state. If not
- * the currently cached item is removed.
- *
- * @param previousResourceVersion null indicates an add
- */
- public synchronized void putResource(T newResource, String previousResourceVersion) {
var resourceId = ResourceID.fromResource(newResource);
- var cachedResource = managedInformerEventSource.get(resourceId).orElse(null);
-
- boolean moveAhead = false;
- if (previousResourceVersion == null && cachedResource == null) {
- if (tombstones.contains(newResource.getMetadata().getUid())) {
- log.debug(
- "Won't resurrect uid {} for resource id: {}",
- newResource.getMetadata().getUid(),
- resourceId);
- return;
- }
- // we can skip further checks as this is a simple add and there's no previous entry to
- // consider
- moveAhead = true;
+
+ if (newResource.getMetadata().getResourceVersion() == null) {
+ log.warn(
+ "Resource {}: with no resourceVersion put in temporary cache. This is not the expected"
+ + " usage pattern, only resources returned from the api server should be put in the"
+ + " cache.",
+ resourceId);
+ return;
}
- if (moveAhead
- || (cachedResource != null
- && (cachedResource
- .getMetadata()
- .getResourceVersion()
- .equals(previousResourceVersion))
- || isLaterResourceVersion(resourceId, newResource, cachedResource))) {
+ // check against the latestResourceVersion processed by the TemporaryResourceCache
+ // If the resource is older, then we can safely ignore.
+ //
+ // this also prevents resurrecting recently deleted entities for which the delete event
+ // has already been processed
+ if (latestResourceVersion != null
+ && ReconcileUtils.compareResourceVersions(
+ latestResourceVersion, newResource.getMetadata().getResourceVersion())
+ > 0) {
log.debug(
- "Temporarily moving ahead to target version {} for resource id: {}",
+ "Resource {}: resourceVersion {} is not later than latest {}",
+ resourceId,
newResource.getMetadata().getResourceVersion(),
- resourceId);
- cache.put(resourceId, newResource);
- } else if (cache.remove(resourceId) != null) {
- log.debug("Removed an obsolete resource from cache for id: {}", resourceId);
+ latestResourceVersion);
+ return;
}
- }
- /**
- * @return true if {@link ConfigurationService#parseResourceVersionsForEventFilteringAndCaching()}
- * is enabled and the resourceVersion of newResource is numerically greater than
- * cachedResource, otherwise false
- */
- public boolean isLaterResourceVersion(ResourceID resourceId, T newResource, T cachedResource) {
- try {
- if (parseResourceVersions
- && Long.parseLong(newResource.getMetadata().getResourceVersion())
- > Long.parseLong(cachedResource.getMetadata().getResourceVersion())) {
- return true;
- }
- } catch (NumberFormatException e) {
+ // also make sure that we're later than the existing temporary entry
+ var cachedResource = getResourceFromCache(resourceId).orElse(null);
+
+ if (cachedResource == null
+ || ReconcileUtils.compareResourceVersions(newResource, cachedResource) > 0) {
log.debug(
- "Could not compare resourceVersions {} and {} for {}",
+ "Temporarily moving ahead to target version {} for resource id: {}",
newResource.getMetadata().getResourceVersion(),
- cachedResource.getMetadata().getResourceVersion(),
resourceId);
+ cache.put(resourceId, newResource);
}
- return false;
}
public synchronized Optional getResourceFromCache(ResourceID resourceID) {
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorIT.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorIT.java
index c87c986f99..e5dae6ca80 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorIT.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorIT.java
@@ -45,7 +45,7 @@ void shouldBePossibleToRetrieveNumberOfRegisteredControllers() {
void shouldBePossibleToRetrieveRegisteredControllerByName() {
final var operator = new Operator();
final var reconciler = new FooReconciler();
- final var name = ReconcilerUtils.getNameFor(reconciler);
+ final var name = ReconcilerUtilsInternal.getNameFor(reconciler);
var registeredControllers = operator.getRegisteredControllers();
assertTrue(operator.getRegisteredController(name).isEmpty());
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternalTest.java
similarity index 84%
rename from operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java
rename to operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternalTest.java
index 3bbe2a894b..12e45b9c23 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsInternalTest.java
@@ -32,17 +32,17 @@
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconciler;
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.getDefaultFinalizerName;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.getDefaultNameFor;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.getDefaultReconcilerName;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.handleKubernetesClientException;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.isFinalizerValid;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getDefaultFinalizerName;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getDefaultNameFor;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.getDefaultReconcilerName;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.handleKubernetesClientException;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.isFinalizerValid;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-class ReconcilerUtilsTest {
+class ReconcilerUtilsInternalTest {
public static final String RESOURCE_URI =
"https://kubernetes.docker.internal:6443/apis/tomcatoperator.io/v1/tomcats";
@@ -71,7 +71,7 @@ void equalsSpecObject() {
var d1 = createTestDeployment();
var d2 = createTestDeployment();
- assertThat(ReconcilerUtils.specsEqual(d1, d2)).isTrue();
+ assertThat(ReconcilerUtilsInternal.specsEqual(d1, d2)).isTrue();
}
@Test
@@ -80,7 +80,7 @@ void equalArbitraryDifferentSpecsOfObjects() {
var d2 = createTestDeployment();
d2.getSpec().getTemplate().getSpec().setHostname("otherhost");
- assertThat(ReconcilerUtils.specsEqual(d1, d2)).isFalse();
+ assertThat(ReconcilerUtilsInternal.specsEqual(d1, d2)).isFalse();
}
@Test
@@ -89,7 +89,7 @@ void getsSpecWithReflection() {
deployment.setSpec(new DeploymentSpec());
deployment.getSpec().setReplicas(5);
- DeploymentSpec spec = (DeploymentSpec) ReconcilerUtils.getSpec(deployment);
+ DeploymentSpec spec = (DeploymentSpec) ReconcilerUtilsInternal.getSpec(deployment);
assertThat(spec.getReplicas()).isEqualTo(5);
}
@@ -97,10 +97,10 @@ void getsSpecWithReflection() {
void properlyHandlesNullSpec() {
Namespace ns = new Namespace();
- final var spec = ReconcilerUtils.getSpec(ns);
+ final var spec = ReconcilerUtilsInternal.getSpec(ns);
assertThat(spec).isNull();
- ReconcilerUtils.setSpec(ns, null);
+ ReconcilerUtilsInternal.setSpec(ns, null);
}
@Test
@@ -111,7 +111,7 @@ void setsSpecWithReflection() {
DeploymentSpec newSpec = new DeploymentSpec();
newSpec.setReplicas(1);
- ReconcilerUtils.setSpec(deployment, newSpec);
+ ReconcilerUtilsInternal.setSpec(deployment, newSpec);
assertThat(deployment.getSpec().getReplicas()).isEqualTo(1);
}
@@ -124,7 +124,7 @@ void setsSpecCustomResourceWithReflection() {
TomcatSpec newSpec = new TomcatSpec();
newSpec.setReplicas(1);
- ReconcilerUtils.setSpec(tomcat, newSpec);
+ ReconcilerUtilsInternal.setSpec(tomcat, newSpec);
assertThat(tomcat.getSpec().getReplicas()).isEqualTo(1);
}
@@ -132,7 +132,7 @@ void setsSpecCustomResourceWithReflection() {
@Test
void loadYamlAsBuilder() {
DeploymentBuilder builder =
- ReconcilerUtils.loadYaml(DeploymentBuilder.class, getClass(), "deployment.yaml");
+ ReconcilerUtilsInternal.loadYaml(DeploymentBuilder.class, getClass(), "deployment.yaml");
builder.accept(ContainerBuilder.class, c -> c.withImage("my-image"));
Deployment deployment = builder.editMetadata().withName("my-deployment").and().build();
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtilsTest.java
new file mode 100644
index 0000000000..675de44a4c
--- /dev/null
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtilsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright Java Operator SDK Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.javaoperatorsdk.operator.api.reconciler;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static io.javaoperatorsdk.operator.api.reconciler.ReconcileUtils.compareResourceVersions;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+class ReconcileUtilsTest {
+
+ private static final Logger log = LoggerFactory.getLogger(ReconcileUtilsTest.class);
+
+ @Test
+ public void compareResourceVersionsTest() {
+ assertThat(compareResourceVersions("11", "22")).isNegative();
+ assertThat(compareResourceVersions("22", "11")).isPositive();
+ assertThat(compareResourceVersions("1", "1")).isZero();
+ assertThat(compareResourceVersions("11", "11")).isZero();
+ assertThat(compareResourceVersions("123", "2")).isPositive();
+ assertThat(compareResourceVersions("3", "211")).isNegative();
+
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("aa", "22"));
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("11", "ba"));
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("", "22"));
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("11", ""));
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("01", "123"));
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("123", "01"));
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("3213", "123a"));
+ assertThrows(
+ NonComparableResourceVersionException.class, () -> compareResourceVersions("321", "123a"));
+ }
+
+ // naive performance test that compares the work case scenario for the parsing and non-parsing
+ // variants
+ @Test
+ @Disabled
+ public void compareResourcePerformanceTest() {
+ var execNum = 30000000;
+ var startTime = System.currentTimeMillis();
+ for (int i = 0; i < execNum; i++) {
+ var res = compareResourceVersions("123456788", "123456789");
+ }
+ var dur1 = System.currentTimeMillis() - startTime;
+ log.info("Duration without parsing: {}", dur1);
+ startTime = System.currentTimeMillis();
+ for (int i = 0; i < execNum; i++) {
+ var res = Long.parseLong("123456788") > Long.parseLong("123456789");
+ }
+ var dur2 = System.currentTimeMillis() - startTime;
+ log.info("Duration with parsing: {}", dur2);
+
+ assertThat(dur1).isLessThan(dur2);
+ }
+}
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java
index bb9d6cf71e..1db69a1f9e 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java
@@ -21,8 +21,10 @@
import org.junit.jupiter.api.Test;
import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
import static org.junit.jupiter.api.Assertions.*;
@@ -31,6 +33,13 @@
class AbstractDependentResourceTest {
+ private static final TestCustomResource PRIMARY = new TestCustomResource();
+ private static final DefaultContext CONTEXT = createContext(PRIMARY);
+
+ private static DefaultContext createContext(TestCustomResource primary) {
+ return new DefaultContext<>(mock(), mock(), primary, false, false);
+ }
+
@Test
void throwsExceptionIfDesiredIsNullOnCreate() {
TestDependentResource testDependentResource = new TestDependentResource();
@@ -38,8 +47,7 @@ void throwsExceptionIfDesiredIsNullOnCreate() {
testDependentResource.setDesired(null);
assertThrows(
- DependentResourceException.class,
- () -> testDependentResource.reconcile(new TestCustomResource(), null));
+ DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
}
@Test
@@ -49,8 +57,7 @@ void throwsExceptionIfDesiredIsNullOnUpdate() {
testDependentResource.setDesired(null);
assertThrows(
- DependentResourceException.class,
- () -> testDependentResource.reconcile(new TestCustomResource(), null));
+ DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
}
@Test
@@ -60,8 +67,7 @@ void throwsExceptionIfCreateReturnsNull() {
testDependentResource.setDesired(configMap());
assertThrows(
- DependentResourceException.class,
- () -> testDependentResource.reconcile(new TestCustomResource(), null));
+ DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
}
@Test
@@ -71,8 +77,28 @@ void throwsExceptionIfUpdateReturnsNull() {
testDependentResource.setDesired(configMap());
assertThrows(
- DependentResourceException.class,
- () -> testDependentResource.reconcile(new TestCustomResource(), null));
+ DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
+ }
+
+ @Test
+ void checkThatDesiredIsOnlyCalledOnce() {
+ final var testDependentResource = new DesiredCallCountCheckingDR();
+ final var primary = new TestCustomResource();
+ final var spec = primary.getSpec();
+ spec.setConfigMapName("foo");
+ spec.setKey("key");
+ spec.setValue("value");
+ final var context = createContext(primary);
+ testDependentResource.reconcile(primary, context);
+
+ spec.setValue("value2");
+ testDependentResource.reconcile(primary, context);
+
+ assertEquals(1, testDependentResource.desiredCallCount);
+
+ context.getOrComputeDesiredStateFor(
+ testDependentResource, p -> testDependentResource.desired(p, context));
+ assertEquals(1, testDependentResource.desiredCallCount);
}
private ConfigMap configMap() {
@@ -130,22 +156,12 @@ protected ConfigMap desired(TestCustomResource primary, Context match(
return result;
}
}
+
+ private static class DesiredCallCountCheckingDR extends TestDependentResource {
+ private short desiredCallCount;
+
+ @Override
+ public ConfigMap update(
+ ConfigMap actual,
+ ConfigMap desired,
+ TestCustomResource primary,
+ Context context) {
+ return desired;
+ }
+
+ @Override
+ public ConfigMap create(
+ ConfigMap desired, TestCustomResource primary, Context context) {
+ return desired;
+ }
+
+ @Override
+ protected ConfigMap desired(TestCustomResource primary, Context context) {
+ final var spec = primary.getSpec();
+ desiredCallCount++;
+ return new ConfigMapBuilder()
+ .editOrNewMetadata()
+ .withName(spec.getConfigMapName())
+ .endMetadata()
+ .addToData(spec.getKey(), spec.getValue())
+ .build();
+ }
+ }
}
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java
index 495fe98416..8dd7283fb9 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java
@@ -18,37 +18,48 @@
import java.util.Map;
import java.util.Optional;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.apps.DeploymentStatusBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.MockKubernetesClient;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher.match;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
@SuppressWarnings({"unchecked"})
class GenericKubernetesResourceMatcherTest {
- private static final Context context = mock(Context.class);
+ private static final Context context = new TestContext();
+
+ private static class TestContext extends DefaultContext {
+ private final KubernetesClient client = MockKubernetesClient.client(HasMetadata.class);
+
+ public TestContext() {
+ this(null);
+ }
+
+ public TestContext(HasMetadata primary) {
+ super(mock(), mock(), primary, false, false);
+ }
+
+ @Override
+ public KubernetesClient getClient() {
+ return client;
+ }
+ }
Deployment actual = createDeployment();
Deployment desired = createDeployment();
TestDependentResource dependentResource = new TestDependentResource(desired);
- @BeforeAll
- static void setUp() {
- final var client = MockKubernetesClient.client(HasMetadata.class);
- when(context.getClient()).thenReturn(client);
- }
-
@Test
void matchesTrivialCases() {
assertThat(GenericKubernetesResourceMatcher.match(desired, actual, context).matched()).isTrue();
@@ -77,9 +88,10 @@ void matchesWithStrongSpecEquality() {
@Test
void doesNotMatchRemovedValues() {
actual = createDeployment();
+ final var localContext = new TestContext(createPrimary("removed"));
assertThat(
GenericKubernetesResourceMatcher.match(
- dependentResource.desired(createPrimary("removed"), null), actual, context)
+ dependentResource.getOrComputeDesired(localContext), actual, localContext)
.matched())
.withFailMessage("Removing values in metadata should lead to a mismatch")
.isFalse();
@@ -186,7 +198,7 @@ ConfigMap createConfigMap() {
}
Deployment createDeployment() {
- return ReconcilerUtils.loadYaml(
+ return ReconcilerUtilsInternal.loadYaml(
Deployment.class, GenericKubernetesResourceMatcherTest.class, "nginx-deployment.yaml");
}
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericResourceUpdaterTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericResourceUpdaterTest.java
index 3b6580c5d3..70d664f652 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericResourceUpdaterTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericResourceUpdaterTest.java
@@ -25,7 +25,7 @@
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.javaoperatorsdk.operator.MockKubernetesClient;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Context;
@@ -131,7 +131,7 @@ void checkServiceAccount() {
}
Deployment createDeployment() {
- return ReconcilerUtils.loadYaml(
+ return ReconcilerUtilsInternal.loadYaml(
Deployment.class, GenericResourceUpdaterTest.class, "nginx-deployment.yaml");
}
}
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java
index bbcfa704b5..c4d2f2c77d 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java
@@ -32,7 +32,7 @@
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.javaoperatorsdk.operator.MockKubernetesClient;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Context;
@@ -419,7 +419,7 @@ void testSortListItems() {
}
private static R loadResource(String fileName, Class clazz) {
- return ReconcilerUtils.loadYaml(
+ return ReconcilerUtilsInternal.loadYaml(
clazz, SSABasedGenericKubernetesResourceMatcherTest.class, fileName);
}
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java
index b0ff06d6b0..460496890a 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java
@@ -23,9 +23,11 @@
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
+import org.mockito.MockedStatic;
import org.mockito.stubbing.Answer;
import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -46,6 +48,7 @@
import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
+import io.javaoperatorsdk.operator.api.reconciler.ReconcileUtils;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
@@ -56,10 +59,8 @@
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
import static io.javaoperatorsdk.operator.TestUtils.markForDeletion;
-import static io.javaoperatorsdk.operator.processing.event.ReconciliationDispatcher.MAX_UPDATE_RETRY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.*;
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -154,28 +155,26 @@ public boolean useFinalizer() {
@Test
void addFinalizerOnNewResource() {
- assertFalse(testCustomResource.hasFinalizer(DEFAULT_FINALIZER));
- reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- verify(reconciler, never()).reconcile(ArgumentMatchers.eq(testCustomResource), any());
- verify(customResourceFacade, times(1))
- .patchResourceWithSSA(
- argThat(testCustomResource -> testCustomResource.hasFinalizer(DEFAULT_FINALIZER)));
+ try (MockedStatic mockedReconcileUtils = mockStatic(ReconcileUtils.class)) {
+ assertFalse(testCustomResource.hasFinalizer(DEFAULT_FINALIZER));
+ reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
+ verify(reconciler, never()).reconcile(ArgumentMatchers.eq(testCustomResource), any());
+ mockedReconcileUtils.verify(() -> ReconcileUtils.addFinalizerWithSSA(any()), times(1));
+ }
}
@Test
void addFinalizerOnNewResourceWithoutSSA() {
- initConfigService(false);
- final ReconciliationDispatcher dispatcher =
- init(testCustomResource, reconciler, null, customResourceFacade, true);
-
- assertFalse(testCustomResource.hasFinalizer(DEFAULT_FINALIZER));
- dispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- verify(reconciler, never()).reconcile(ArgumentMatchers.eq(testCustomResource), any());
- verify(customResourceFacade, times(1))
- .patchResource(
- argThat(testCustomResource -> testCustomResource.hasFinalizer(DEFAULT_FINALIZER)),
- any());
- assertThat(testCustomResource.hasFinalizer(DEFAULT_FINALIZER)).isTrue();
+ try (MockedStatic mockedReconcileUtils = mockStatic(ReconcileUtils.class)) {
+ initConfigService(false, false);
+ final ReconciliationDispatcher dispatcher =
+ init(testCustomResource, reconciler, null, customResourceFacade, true);
+
+ assertFalse(testCustomResource.hasFinalizer(DEFAULT_FINALIZER));
+ dispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
+ verify(reconciler, never()).reconcile(ArgumentMatchers.eq(testCustomResource), any());
+ mockedReconcileUtils.verify(() -> ReconcileUtils.addFinalizer(any()), times(1));
+ }
}
@Test
@@ -190,13 +189,13 @@ void patchesBothResourceAndStatusIfFinalizerSet() {
testCustomResource.addFinalizer(DEFAULT_FINALIZER);
reconciler.reconcile = (r, c) -> UpdateControl.patchResourceAndStatus(testCustomResource);
- when(customResourceFacade.patchResource(eq(testCustomResource), any()))
+ when(customResourceFacade.patchResource(any(), eq(testCustomResource), any()))
.thenReturn(testCustomResource);
reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- verify(customResourceFacade, times(1)).patchResource(eq(testCustomResource), any());
- verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
+ verify(customResourceFacade, times(1)).patchResource(any(), eq(testCustomResource), any());
+ verify(customResourceFacade, times(1)).patchStatus(any(), eq(testCustomResource), any());
}
@Test
@@ -207,8 +206,8 @@ void patchesStatus() {
reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
- verify(customResourceFacade, never()).patchResource(any(), any());
+ verify(customResourceFacade, times(1)).patchStatus(any(), eq(testCustomResource), any());
+ verify(customResourceFacade, never()).patchResource(any(), any(), any());
}
@Test
@@ -232,16 +231,19 @@ void callsDeleteIfObjectHasFinalizerAndMarkedForDelete() {
@Test
void removesDefaultFinalizerOnDeleteIfSet() {
- testCustomResource.addFinalizer(DEFAULT_FINALIZER);
- markForDeletion(testCustomResource);
+ try (MockedStatic mockedReconcileUtils = mockStatic(ReconcileUtils.class)) {
+ testCustomResource.addFinalizer(DEFAULT_FINALIZER);
+ markForDeletion(testCustomResource);
- var postExecControl =
- reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
+ var postExecControl =
+ reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- assertThat(postExecControl.isFinalizerRemoved()).isTrue();
- verify(customResourceFacade, times(1)).patchResourceWithoutSSA(eq(testCustomResource), any());
+ assertThat(postExecControl.isFinalizerRemoved()).isTrue();
+ mockedReconcileUtils.verify(() -> ReconcileUtils.removeFinalizer(any()), times(1));
+ }
}
+ @Disabled("todo move to ReconcileUtils test")
@Test
void retriesFinalizerRemovalWithFreshResource() {
testCustomResource.addFinalizer(DEFAULT_FINALIZER);
@@ -261,6 +263,8 @@ void retriesFinalizerRemovalWithFreshResource() {
verify(customResourceFacade, times(1)).getResource(any(), any());
}
+ // TODO move to utils test
+ @Disabled
@Test
void nullResourceIsGracefullyHandledOnFinalizerRemovalRetry() {
// simulate the operator not able or not be allowed to get the custom resource during the retry
@@ -279,41 +283,6 @@ void nullResourceIsGracefullyHandledOnFinalizerRemovalRetry() {
verify(customResourceFacade, times(1)).getResource(any(), any());
}
- @Test
- void throwsExceptionIfFinalizerRemovalRetryExceeded() {
- testCustomResource.addFinalizer(DEFAULT_FINALIZER);
- markForDeletion(testCustomResource);
- when(customResourceFacade.patchResourceWithoutSSA(any(), any()))
- .thenThrow(new KubernetesClientException(null, 409, null));
- when(customResourceFacade.getResource(any(), any()))
- .thenAnswer((Answer) invocationOnMock -> createResourceWithFinalizer());
-
- var postExecControl =
- reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
-
- assertThat(postExecControl.isFinalizerRemoved()).isFalse();
- assertThat(postExecControl.getRuntimeException()).isPresent();
- assertThat(postExecControl.getRuntimeException().get()).isInstanceOf(OperatorException.class);
- verify(customResourceFacade, times(MAX_UPDATE_RETRY)).patchResourceWithoutSSA(any(), any());
- verify(customResourceFacade, times(MAX_UPDATE_RETRY - 1)).getResource(any(), any());
- }
-
- @Test
- void throwsExceptionIfFinalizerRemovalClientExceptionIsNotConflict() {
- testCustomResource.addFinalizer(DEFAULT_FINALIZER);
- markForDeletion(testCustomResource);
- when(customResourceFacade.patchResourceWithoutSSA(any(), any()))
- .thenThrow(new KubernetesClientException(null, 400, null));
-
- var res =
- reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
-
- assertThat(res.getRuntimeException()).isPresent();
- assertThat(res.getRuntimeException().get()).isInstanceOf(KubernetesClientException.class);
- verify(customResourceFacade, times(1)).patchResourceWithoutSSA(any(), any());
- verify(customResourceFacade, never()).getResource(any(), any());
- }
-
@Test
void doesNotCallDeleteOnControllerIfMarkedForDeletionWhenNoFinalizerIsConfigured() {
final ReconciliationDispatcher dispatcher =
@@ -354,7 +323,7 @@ void doesNotRemovesTheSetFinalizerIfTheDeleteNotMethodInstructsIt() {
reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
assertEquals(1, testCustomResource.getMetadata().getFinalizers().size());
- verify(customResourceFacade, never()).patchResource(any(), any());
+ verify(customResourceFacade, never()).patchResource(any(), any(), any());
}
@Test
@@ -364,23 +333,25 @@ void doesNotUpdateTheResourceIfNoUpdateUpdateControlIfFinalizerSet() {
reconciler.reconcile = (r, c) -> UpdateControl.noUpdate();
reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- verify(customResourceFacade, never()).patchResource(any(), any());
- verify(customResourceFacade, never()).patchStatus(eq(testCustomResource), any());
+ verify(customResourceFacade, never()).patchResource(any(), any(), any());
+ verify(customResourceFacade, never()).patchStatus(any(), eq(testCustomResource), any());
}
@Test
void addsFinalizerIfNotMarkedForDeletionAndEmptyCustomResourceReturned() {
- removeFinalizers(testCustomResource);
- reconciler.reconcile = (r, c) -> UpdateControl.noUpdate();
- when(customResourceFacade.patchResourceWithSSA(any())).thenReturn(testCustomResource);
-
- var postExecControl =
- reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
-
- verify(customResourceFacade, times(1))
- .patchResourceWithSSA(argThat(a -> !a.getMetadata().getFinalizers().isEmpty()));
- assertThat(postExecControl.updateIsStatusPatch()).isFalse();
- assertThat(postExecControl.getUpdatedCustomResource()).isPresent();
+ try (MockedStatic mockedReconcileUtils = mockStatic(ReconcileUtils.class)) {
+ removeFinalizers(testCustomResource);
+ reconciler.reconcile = (r, c) -> UpdateControl.noUpdate();
+ mockedReconcileUtils
+ .when(() -> ReconcileUtils.addFinalizerWithSSA(any()))
+ .thenReturn(testCustomResource);
+ var postExecControl =
+ reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
+
+ mockedReconcileUtils.verify(() -> ReconcileUtils.addFinalizerWithSSA(any()), times(1));
+ assertThat(postExecControl.updateIsStatusPatch()).isFalse();
+ assertThat(postExecControl.getUpdatedCustomResource()).isPresent();
+ }
}
@Test
@@ -390,7 +361,7 @@ void doesNotCallDeleteIfMarkedForDeletionButNotOurFinalizer() {
reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- verify(customResourceFacade, never()).patchResource(any(), any());
+ verify(customResourceFacade, never()).patchResource(any(), any(), any());
verify(reconciler, never()).cleanup(eq(testCustomResource), any());
}
@@ -471,7 +442,7 @@ void doesNotUpdatesObservedGenerationIfStatusIsNotPatchedWhenUsingSSA() throws E
CustomResourceFacade facade = mock(CustomResourceFacade.class);
when(config.isGenerationAware()).thenReturn(true);
when(reconciler.reconcile(any(), any())).thenReturn(UpdateControl.noUpdate());
- when(facade.patchStatus(any(), any())).thenReturn(observedGenResource);
+ when(facade.patchStatus(any(), any(), any())).thenReturn(observedGenResource);
var dispatcher = init(observedGenResource, reconciler, config, facade, true);
PostExecutionControl control =
@@ -489,12 +460,12 @@ void doesNotPatchObservedGenerationOnCustomResourcePatch() throws Exception {
when(config.isGenerationAware()).thenReturn(true);
when(reconciler.reconcile(any(), any()))
.thenReturn(UpdateControl.patchResource(observedGenResource));
- when(facade.patchResource(any(), any())).thenReturn(observedGenResource);
+ when(facade.patchResource(any(), any(), any())).thenReturn(observedGenResource);
var dispatcher = init(observedGenResource, reconciler, config, facade, false);
dispatcher.handleExecution(executionScopeWithCREvent(observedGenResource));
- verify(facade, never()).patchStatus(any(), any());
+ verify(facade, never()).patchStatus(any(), any(), any());
}
@Test
@@ -529,7 +500,7 @@ public boolean isLastAttempt() {
false)
.setResource(testCustomResource));
- verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
+ verify(customResourceFacade, times(1)).patchStatus(any(), eq(testCustomResource), any());
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource), any(), any());
}
@@ -550,7 +521,7 @@ void callErrorStatusHandlerEvenOnFirstError() {
var postExecControl =
reconciliationDispatcher.handleExecution(
new ExecutionScope(null, null, false, false).setResource(testCustomResource));
- verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
+ verify(customResourceFacade, times(1)).patchStatus(any(), eq(testCustomResource), any());
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource), any(), any());
assertThat(postExecControl.exceptionDuringExecution()).isTrue();
}
@@ -573,7 +544,7 @@ void errorHandlerCanInstructNoRetryWithUpdate() {
new ExecutionScope(null, null, false, false).setResource(testCustomResource));
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource), any(), any());
- verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
+ verify(customResourceFacade, times(1)).patchStatus(any(), eq(testCustomResource), any());
assertThat(postExecControl.exceptionDuringExecution()).isFalse();
}
@@ -595,7 +566,7 @@ void errorHandlerCanInstructNoRetryNoUpdate() {
new ExecutionScope(null, null, false, false).setResource(testCustomResource));
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource), any(), any());
- verify(customResourceFacade, times(0)).patchStatus(eq(testCustomResource), any());
+ verify(customResourceFacade, times(0)).patchStatus(any(), eq(testCustomResource), any());
assertThat(postExecControl.exceptionDuringExecution()).isFalse();
}
@@ -611,7 +582,7 @@ void errorStatusHandlerCanPatchResource() {
reconciliationDispatcher.handleExecution(
new ExecutionScope(null, null, false, false).setResource(testCustomResource));
- verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
+ verify(customResourceFacade, times(1)).patchStatus(any(), eq(testCustomResource), any());
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource), any(), any());
}
@@ -660,6 +631,7 @@ void canSkipSchedulingMaxDelayIf() {
}
@Test
+ @Disabled("Move to reconciler utils test")
void retriesAddingFinalizerWithoutSSA() {
initConfigService(false);
reconciliationDispatcher =
@@ -667,7 +639,7 @@ void retriesAddingFinalizerWithoutSSA() {
removeFinalizers(testCustomResource);
reconciler.reconcile = (r, c) -> UpdateControl.noUpdate();
- when(customResourceFacade.patchResource(any(), any()))
+ when(customResourceFacade.patchResourceWithoutSSA(any(), any()))
.thenThrow(new KubernetesClientException(null, 409, null))
.thenReturn(testCustomResource);
when(customResourceFacade.getResource(any(), any()))
@@ -680,7 +652,7 @@ void retriesAddingFinalizerWithoutSSA() {
reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
- verify(customResourceFacade, times(2)).patchResource(any(), any());
+ verify(customResourceFacade, times(2)).patchResourceWithoutSSA(any(), any());
}
@Test
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java
index dcd10b4225..14a4526698 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java
@@ -22,7 +22,7 @@
import org.junit.jupiter.api.Test;
import io.javaoperatorsdk.operator.MockKubernetesClient;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.TestUtils;
import io.javaoperatorsdk.operator.api.config.BaseConfigurationService;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
@@ -46,7 +46,7 @@ class ControllerEventSourceTest
extends AbstractEventSourceTestBase, EventHandler> {
public static final String FINALIZER =
- ReconcilerUtils.getDefaultFinalizerName(TestCustomResource.class);
+ ReconcilerUtilsInternal.getDefaultFinalizerName(TestCustomResource.class);
private final TestController testController = new TestController(true);
private final ControllerConfiguration controllerConfig = mock(ControllerConfiguration.class);
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java
index 208d6aeaaa..f54e47304b 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java
@@ -94,31 +94,18 @@ public synchronized void start() {}
}
@Test
- void skipsEventPropagationIfResourceWithSameVersionInResourceCache() {
+ void skipsEventPropagation() {
when(temporaryResourceCacheMock.getResourceFromCache(any()))
.thenReturn(Optional.of(testDeployment()));
+ when(temporaryResourceCacheMock.onAddOrUpdateEvent(any())).thenReturn(true);
+
informerEventSource.onAdd(testDeployment());
informerEventSource.onUpdate(testDeployment(), testDeployment());
verify(eventHandlerMock, never()).handleEvent(any());
}
- @Test
- void skipsAddEventPropagationViaAnnotation() {
- informerEventSource.onAdd(informerEventSource.addPreviousAnnotation(null, testDeployment()));
-
- verify(eventHandlerMock, never()).handleEvent(any());
- }
-
- @Test
- void skipsUpdateEventPropagationViaAnnotation() {
- informerEventSource.onUpdate(
- testDeployment(), informerEventSource.addPreviousAnnotation("1", testDeployment()));
-
- verify(eventHandlerMock, never()).handleEvent(any());
- }
-
@Test
void processEventPropagationWithoutAnnotation() {
informerEventSource.onUpdate(testDeployment(), testDeployment());
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryPrimaryResourceCacheTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryPrimaryResourceCacheTest.java
index e3dc2c82e4..4b12148015 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryPrimaryResourceCacheTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryPrimaryResourceCacheTest.java
@@ -16,10 +16,10 @@
package io.javaoperatorsdk.operator.processing.event.source.informer;
import java.util.Map;
-import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -27,49 +27,40 @@
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
-import io.javaoperatorsdk.operator.processing.event.source.informer.TemporaryResourceCache.ExpirationCache;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
class TemporaryPrimaryResourceCacheTest {
public static final String RESOURCE_VERSION = "2";
- @SuppressWarnings("unchecked")
- private InformerEventSource informerEventSource;
-
private TemporaryResourceCache temporaryResourceCache;
@BeforeEach
void setup() {
- informerEventSource = mock(InformerEventSource.class);
- temporaryResourceCache = new TemporaryResourceCache<>(informerEventSource, false);
+ temporaryResourceCache = new TemporaryResourceCache<>(true);
}
@Test
void updateAddsTheResourceIntoCacheIfTheInformerHasThePreviousResourceVersion() {
var testResource = testResource();
var prevTestResource = testResource();
- prevTestResource.getMetadata().setResourceVersion("0");
- when(informerEventSource.get(any())).thenReturn(Optional.of(prevTestResource));
+ prevTestResource.getMetadata().setResourceVersion("1");
- temporaryResourceCache.putResource(testResource, "0");
+ temporaryResourceCache.putResource(testResource);
var cached = temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource));
assertThat(cached).isPresent();
}
@Test
- void updateNotAddsTheResourceIntoCacheIfTheInformerHasOtherVersion() {
+ void updateNotAddsTheResourceIntoCacheIfLaterVersionKnown() {
var testResource = testResource();
- var informerCachedResource = testResource();
- informerCachedResource.getMetadata().setResourceVersion("x");
- when(informerEventSource.get(any())).thenReturn(Optional.of(informerCachedResource));
- temporaryResourceCache.putResource(testResource, "0");
+ temporaryResourceCache.onAddOrUpdateEvent(
+ testResource.toBuilder().editMetadata().withResourceVersion("3").endMetadata().build());
+
+ temporaryResourceCache.putResource(testResource);
var cached = temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource));
assertThat(cached).isNotPresent();
@@ -78,9 +69,8 @@ void updateNotAddsTheResourceIntoCacheIfTheInformerHasOtherVersion() {
@Test
void addOperationAddsTheResourceIfInformerCacheStillEmpty() {
var testResource = testResource();
- when(informerEventSource.get(any())).thenReturn(Optional.empty());
- temporaryResourceCache.putAddedResource(testResource);
+ temporaryResourceCache.putResource(testResource);
var cached = temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource));
assertThat(cached).isPresent();
@@ -89,46 +79,79 @@ void addOperationAddsTheResourceIfInformerCacheStillEmpty() {
@Test
void addOperationNotAddsTheResourceIfInformerCacheNotEmpty() {
var testResource = testResource();
- when(informerEventSource.get(any())).thenReturn(Optional.of(testResource()));
- temporaryResourceCache.putAddedResource(testResource);
+ temporaryResourceCache.putResource(testResource);
+
+ temporaryResourceCache.putResource(
+ new ConfigMapBuilder(testResource)
+ .editMetadata()
+ .withResourceVersion("1")
+ .endMetadata()
+ .build());
var cached = temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource));
- assertThat(cached).isNotPresent();
+ assertThat(cached.orElseThrow().getMetadata().getResourceVersion()).isEqualTo(RESOURCE_VERSION);
}
@Test
void removesResourceFromCache() {
ConfigMap testResource = propagateTestResourceToCache();
- temporaryResourceCache.onAddOrUpdateEvent(testResource());
+ temporaryResourceCache.onAddOrUpdateEvent(
+ new ConfigMapBuilder(testResource)
+ .editMetadata()
+ .withResourceVersion("3")
+ .endMetadata()
+ .build());
assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
.isNotPresent();
}
@Test
- void resourceVersionParsing() {
- this.temporaryResourceCache = new TemporaryResourceCache<>(informerEventSource, true);
+ void nonComparableResourceVersionsDisables() {
+ this.temporaryResourceCache = new TemporaryResourceCache<>(false);
- ConfigMap testResource = propagateTestResourceToCache();
+ this.temporaryResourceCache.putResource(testResource());
- // an event with a newer version will not remove
- temporaryResourceCache.onAddOrUpdateEvent(
- new ConfigMapBuilder(testResource)
- .editMetadata()
- .withResourceVersion("1")
- .endMetadata()
- .build());
+ assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource())))
+ .isEmpty();
+ }
- assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
- .isPresent();
+ @Test
+ void lockedEventBeforePut() throws Exception {
+ var testResource = testResource();
- // anything else will remove
- temporaryResourceCache.onAddOrUpdateEvent(testResource());
+ temporaryResourceCache.startModifying(ResourceID.fromResource(testResource));
- assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
- .isNotPresent();
+ ExecutorService ex = Executors.newSingleThreadExecutor();
+ try {
+ var result = ex.submit(() -> temporaryResourceCache.onAddOrUpdateEvent(testResource));
+
+ temporaryResourceCache.putResource(testResource);
+ assertThat(result.isDone()).isFalse();
+ temporaryResourceCache.doneModifying(ResourceID.fromResource(testResource));
+ assertThat(result.get(10, TimeUnit.SECONDS)).isTrue();
+ } finally {
+ ex.shutdownNow();
+ }
+ }
+
+ @Test
+ void putBeforeEvent() {
+ var testResource = testResource();
+
+ // first ensure an event is not known
+ var result = temporaryResourceCache.onAddOrUpdateEvent(testResource);
+ assertThat(result).isFalse();
+
+ var nextResource = testResource();
+ nextResource.getMetadata().setResourceVersion("3");
+ temporaryResourceCache.putResource(nextResource);
+
+ // now expect an event with the matching resourceVersion to be known after the put
+ result = temporaryResourceCache.onAddOrUpdateEvent(nextResource);
+ assertThat(result).isTrue();
}
@Test
@@ -143,45 +166,15 @@ void rapidDeletion() {
.endMetadata()
.build(),
false);
- temporaryResourceCache.putAddedResource(testResource);
+ temporaryResourceCache.putResource(testResource);
assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
.isEmpty();
}
- @Test
- void expirationCacheMax() {
- ExpirationCache cache = new ExpirationCache<>(2, Integer.MAX_VALUE);
-
- cache.add(1);
- cache.add(2);
- cache.add(3);
-
- assertThat(cache.contains(1)).isFalse();
- assertThat(cache.contains(2)).isTrue();
- assertThat(cache.contains(3)).isTrue();
- }
-
- @Test
- void expirationCacheTtl() {
- ExpirationCache cache = new ExpirationCache<>(2, 1);
-
- cache.add(1);
- cache.add(2);
-
- Awaitility.await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- assertThat(cache.contains(1)).isFalse();
- assertThat(cache.contains(2)).isFalse();
- });
- }
-
private ConfigMap propagateTestResourceToCache() {
var testResource = testResource();
- when(informerEventSource.get(any())).thenReturn(Optional.empty());
- temporaryResourceCache.putAddedResource(testResource);
+ temporaryResourceCache.putResource(testResource);
assertThat(temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(testResource)))
.isPresent();
return testResource;
diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml
index e3072dd17a..60c235a9ec 100644
--- a/operator-framework-junit5/pom.xml
+++ b/operator-framework-junit5/pom.xml
@@ -21,7 +21,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
operator-framework-junit-5
diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java
index 0a33293b53..1faa545f54 100644
--- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java
+++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java
@@ -44,7 +44,7 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.LocalPortForward;
import io.javaoperatorsdk.operator.Operator;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.RegisteredController;
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider;
import io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider;
@@ -140,7 +140,7 @@ public static Builder builder() {
}
public static void applyCrd(Class extends HasMetadata> resourceClass, KubernetesClient client) {
- applyCrd(ReconcilerUtils.getResourceTypeName(resourceClass), client);
+ applyCrd(ReconcilerUtilsInternal.getResourceTypeName(resourceClass), client);
}
/**
@@ -192,7 +192,7 @@ private static void applyCrd(String crdString, String path, KubernetesClient cli
* @param crClass the custom resource class for which we want to apply the CRD
*/
public void applyCrd(Class extends CustomResource> crClass) {
- applyCrd(ReconcilerUtils.getResourceTypeName(crClass));
+ applyCrd(ReconcilerUtilsInternal.getResourceTypeName(crClass));
}
/**
@@ -203,7 +203,7 @@ public void applyCrd(Class extends CustomResource> crClass) {
*
* @param resourceTypeName the resource type name associated with the CRD to be applied,
* typically, given a resource type, its name would be obtained using {@link
- * ReconcilerUtils#getResourceTypeName(Class)}
+ * ReconcilerUtilsInternal#getResourceTypeName(Class)}
*/
public void applyCrd(String resourceTypeName) {
// first attempt to use a manually defined CRD
@@ -296,7 +296,7 @@ protected void before(ExtensionContext context) {
ref.controllerConfigurationOverrider.accept(oconfig);
}
- final var resourceTypeName = ReconcilerUtils.getResourceTypeName(resourceClass);
+ final var resourceTypeName = ReconcilerUtilsInternal.getResourceTypeName(resourceClass);
// only try to apply a CRD for the reconciler if it is associated to a CR
if (CustomResource.class.isAssignableFrom(resourceClass)) {
applyCrd(resourceTypeName);
diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml
index 7b4a12824d..253907eb1e 100644
--- a/operator-framework/pom.xml
+++ b/operator-framework/pom.xml
@@ -21,7 +21,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
operator-framework
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/LeaderElectionPermissionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/LeaderElectionPermissionIT.java
index db99324ae2..457de54ca3 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/LeaderElectionPermissionIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/LeaderElectionPermissionIT.java
@@ -26,7 +26,7 @@
import io.javaoperatorsdk.annotation.Sample;
import io.javaoperatorsdk.operator.Operator;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
@@ -87,14 +87,14 @@ public UpdateControl reconcile(ConfigMap resource, Context
private void applyRoleBinding() {
var clusterRoleBinding =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
RoleBinding.class, this.getClass(), "leader-elector-stop-noaccess-role-binding.yaml");
adminClient.resource(clusterRoleBinding).createOrReplace();
}
private void applyRole() {
var role =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
Role.class, this.getClass(), "leader-elector-stop-role-noaccess.yaml");
adminClient.resource(role).createOrReplace();
}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/builtinresourcecleaner/BuiltInResourceCleanerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/builtinresourcecleaner/BuiltInResourceCleanerIT.java
index 9667c22486..18e076e2bf 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/builtinresourcecleaner/BuiltInResourceCleanerIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/builtinresourcecleaner/BuiltInResourceCleanerIT.java
@@ -24,7 +24,7 @@
import io.fabric8.kubernetes.api.model.Service;
import io.javaoperatorsdk.annotation.Sample;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.dependent.standalonedependent.StandaloneDependentResourceIT;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
@@ -85,7 +85,7 @@ void cleanerIsCalledOnBuiltInResource() {
Service testService() {
Service service =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
Service.class,
StandaloneDependentResourceIT.class,
"/io/javaoperatorsdk/operator/service-template.yaml");
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/PreviousAnnotationDisabledIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/ComparableResourceVersionsDisabledIT.java
similarity index 94%
rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/PreviousAnnotationDisabledIT.java
rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/ComparableResourceVersionsDisabledIT.java
index 17fe6b7125..6577d4ca59 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/PreviousAnnotationDisabledIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/ComparableResourceVersionsDisabledIT.java
@@ -34,9 +34,7 @@ class PreviousAnnotationDisabledIT {
@RegisterExtension
LocallyRunOperatorExtension operator =
LocallyRunOperatorExtension.builder()
- .withReconciler(new CreateUpdateEventFilterTestReconciler())
- .withConfigurationService(
- overrider -> overrider.withPreviousAnnotationForDependentResources(false))
+ .withReconciler(new CreateUpdateEventFilterTestReconciler(false))
.build();
@Test
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java
index 40bf2cc350..4344356ff9 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateEventFilterTestReconciler.java
@@ -41,6 +41,16 @@ public class CreateUpdateEventFilterTestReconciler
private final DirectConfigMapDependentResource configMapDR =
new DirectConfigMapDependentResource(ConfigMap.class);
+ private final boolean comparableResourceVersion;
+
+ public CreateUpdateEventFilterTestReconciler(boolean comparableResourceVersion) {
+ this.comparableResourceVersion = comparableResourceVersion;
+ }
+
+ public CreateUpdateEventFilterTestReconciler() {
+ this(true);
+ }
+
@Override
public UpdateControl reconcile(
CreateUpdateEventFilterTestCustomResource resource,
@@ -89,6 +99,7 @@ public List> prepareEv
InformerEventSourceConfiguration.from(
ConfigMap.class, CreateUpdateEventFilterTestCustomResource.class)
.withLabelSelector("integrationtest = " + this.getClass().getSimpleName())
+ .withComparableResourceVersion(comparableResourceVersion)
.build();
final var informerEventSource = new InformerEventSource<>(informerConfiguration, context);
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/infrastructureclient/InfrastructureClientIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/infrastructureclient/InfrastructureClientIT.java
index 59faaae90b..eb39fa0657 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/infrastructureclient/InfrastructureClientIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/infrastructureclient/InfrastructureClientIT.java
@@ -28,7 +28,7 @@
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import static org.assertj.core.api.Assertions.assertThat;
@@ -127,23 +127,25 @@ void shouldNotAccessNotPermittedResources() {
private void applyClusterRoleBinding(String filename) {
var clusterRoleBinding =
- ReconcilerUtils.loadYaml(ClusterRoleBinding.class, this.getClass(), filename);
+ ReconcilerUtilsInternal.loadYaml(ClusterRoleBinding.class, this.getClass(), filename);
operator.getInfrastructureKubernetesClient().resource(clusterRoleBinding).serverSideApply();
}
private void applyClusterRole(String filename) {
- var clusterRole = ReconcilerUtils.loadYaml(ClusterRole.class, this.getClass(), filename);
+ var clusterRole =
+ ReconcilerUtilsInternal.loadYaml(ClusterRole.class, this.getClass(), filename);
operator.getInfrastructureKubernetesClient().resource(clusterRole).serverSideApply();
}
private void removeClusterRoleBinding(String filename) {
var clusterRoleBinding =
- ReconcilerUtils.loadYaml(ClusterRoleBinding.class, this.getClass(), filename);
+ ReconcilerUtilsInternal.loadYaml(ClusterRoleBinding.class, this.getClass(), filename);
operator.getInfrastructureKubernetesClient().resource(clusterRoleBinding).delete();
}
private void removeClusterRole(String filename) {
- var clusterRole = ReconcilerUtils.loadYaml(ClusterRole.class, this.getClass(), filename);
+ var clusterRole =
+ ReconcilerUtilsInternal.loadYaml(ClusterRole.class, this.getClass(), filename);
operator.getInfrastructureKubernetesClient().resource(clusterRole).delete();
}
}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java
index e091896597..eb19f9e249 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java
@@ -45,7 +45,7 @@ public UpdateControl reconcile(
Context context) {
numberOfExecutions.addAndGet(1);
- log.info("Value: " + resource.getSpec().getValue());
+ log.info("Value: {}", resource.getSpec().getValue());
if (removeAnnotation) {
resource.getMetadata().getAnnotations().remove(TEST_ANNOTATION);
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/TestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/TestReconciler.java
index b614b97f3a..6bb184d374 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/TestReconciler.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/TestReconciler.java
@@ -25,7 +25,7 @@
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;
@@ -38,7 +38,7 @@ public class TestReconciler
private static final Logger log = LoggerFactory.getLogger(TestReconciler.class);
public static final String FINALIZER_NAME =
- ReconcilerUtils.getDefaultFinalizerName(TestCustomResource.class);
+ ReconcilerUtilsInternal.getDefaultFinalizerName(TestCustomResource.class);
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
private final AtomicInteger numberOfCleanupExecutions = new AtomicInteger(0);
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java
index 370f09509f..ffd0f6b904 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java
@@ -29,7 +29,7 @@
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Service;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable;
import io.javaoperatorsdk.operator.api.config.BaseConfigurationService;
import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter;
@@ -133,13 +133,13 @@ void missingAnnotationCreatesDefaultConfig() {
final var reconciler = new MissingAnnotationReconciler();
var config = configFor(reconciler);
- assertThat(config.getName()).isEqualTo(ReconcilerUtils.getNameFor(reconciler));
+ assertThat(config.getName()).isEqualTo(ReconcilerUtilsInternal.getNameFor(reconciler));
assertThat(config.getRetry()).isInstanceOf(GenericRetry.class);
assertThat(config.getRateLimiter()).isInstanceOf(LinearRateLimiter.class);
assertThat(config.maxReconciliationInterval()).hasValue(Duration.ofHours(DEFAULT_INTERVAL));
assertThat(config.fieldManager()).isEqualTo(config.getName());
assertThat(config.getFinalizerName())
- .isEqualTo(ReconcilerUtils.getDefaultFinalizerName(config.getResourceClass()));
+ .isEqualTo(ReconcilerUtilsInternal.getDefaultFinalizerName(config.getResourceClass()));
final var informerConfig = config.getInformerConfig();
assertThat(informerConfig.getLabelSelector()).isNull();
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationServiceTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationServiceTest.java
index 1b328ccaf9..fa31575b9e 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationServiceTest.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationServiceTest.java
@@ -20,7 +20,7 @@
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
@@ -40,7 +40,7 @@ void returnsValuesFromControllerAnnotationFinalizer() {
assertEquals(
CustomResource.getCRDName(TestCustomResource.class), configuration.getResourceTypeName());
assertEquals(
- ReconcilerUtils.getDefaultFinalizerName(TestCustomResource.class),
+ ReconcilerUtilsInternal.getDefaultFinalizerName(TestCustomResource.class),
configuration.getFinalizerName());
assertEquals(TestCustomResource.class, configuration.getResourceClass());
assertFalse(configuration.isGenerationAware());
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateReconciler.java
index de485cfc4e..5a9d9a7f06 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateReconciler.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateReconciler.java
@@ -104,13 +104,13 @@ private void createExternalResource(
.withData(Map.of(ID_KEY, createdResource.getId()))
.build();
configMap.addOwnerReference(resource);
- context.getClient().configMaps().resource(configMap).create();
var primaryID = ResourceID.fromResource(resource);
// Making sure that the created resources are in the cache for the next reconciliation.
// This is critical in this case, since on next reconciliation if it would not be in the cache
// it would be created again.
- configMapEventSource.handleRecentResourceCreate(primaryID, configMap);
+ configMapEventSource.eventFilteringUpdateAndCacheResource(
+ configMap, toCreate -> context.getClient().configMaps().resource(toCreate).create());
externalResourceEventSource.handleRecentResourceCreate(primaryID, createdResource);
}
@@ -128,6 +128,7 @@ public DeleteControl cleanup(
return DeleteControl.defaultDelete();
}
+ @Override
public int getNumberOfExecutions() {
return numberOfExecutions.get();
}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java
index 221d7363a3..ce98af58e0 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java
@@ -34,7 +34,7 @@
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
import io.javaoperatorsdk.operator.Operator;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.health.InformerHealthIndicator;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
@@ -399,23 +399,25 @@ private void setFullResourcesAccess() {
private void addRoleBindingsToTestNamespaces() {
var role =
- ReconcilerUtils.loadYaml(Role.class, this.getClass(), "rbac-test-only-main-ns-access.yaml");
+ ReconcilerUtilsInternal.loadYaml(
+ Role.class, this.getClass(), "rbac-test-only-main-ns-access.yaml");
adminClient.resource(role).inNamespace(actualNamespace).createOrReplace();
var roleBinding =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
RoleBinding.class, this.getClass(), "rbac-test-only-main-ns-access-binding.yaml");
adminClient.resource(roleBinding).inNamespace(actualNamespace).createOrReplace();
}
private void applyClusterRoleBinding() {
var clusterRoleBinding =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
ClusterRoleBinding.class, this.getClass(), "rbac-test-role-binding.yaml");
adminClient.resource(clusterRoleBinding).createOrReplace();
}
private void applyClusterRole(String filename) {
- var clusterRole = ReconcilerUtils.loadYaml(ClusterRole.class, this.getClass(), filename);
+ var clusterRole =
+ ReconcilerUtilsInternal.loadYaml(ClusterRole.class, this.getClass(), filename);
adminClient.resource(clusterRole).createOrReplace();
}
@@ -431,7 +433,7 @@ private Namespace namespace(String name) {
private void removeClusterRoleBinding() {
var clusterRoleBinding =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
ClusterRoleBinding.class, this.getClass(), "rbac-test-role-binding.yaml");
adminClient.resource(clusterRoleBinding).delete();
}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java
index 1bb34de16c..fb243251f3 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceDependentResource.java
@@ -26,7 +26,7 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.loadYaml;
@KubernetesDependent
public class ServiceDependentResource
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java
index 7cd65bd7ef..6a998b3ea4 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/ssalegacymatcher/ServiceDependentResource.java
@@ -25,7 +25,7 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.loadYaml;
@KubernetesDependent
public class ServiceDependentResource
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java
index 6f97be1be7..92f033d681 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentTestReconciler.java
@@ -20,7 +20,7 @@
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.KubernetesClientException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
@@ -90,7 +90,7 @@ protected Deployment desired(
StandaloneDependentTestCustomResource primary,
Context context) {
Deployment deployment =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
Deployment.class,
StandaloneDependentResourceIT.class,
"/io/javaoperatorsdk/operator/nginx-deployment.yaml");
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java
index e86c772cda..e4bcaac460 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerDependentResource.java
@@ -17,7 +17,7 @@
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
@@ -32,7 +32,7 @@ protected StatefulSet desired(
StatefulSetDesiredSanitizerCustomResource primary,
Context context) {
var template =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
StatefulSet.class, getClass(), "/io/javaoperatorsdk/operator/statefulset.yaml");
template.setMetadata(
new ObjectMetaBuilder()
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseService.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseService.java
index 06abcc0889..7a0d50debf 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseService.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseService.java
@@ -19,7 +19,7 @@
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.workflow.complexdependent.ComplexWorkflowCustomResource;
@@ -33,7 +33,7 @@ public BaseService(String component) {
protected Service desired(
ComplexWorkflowCustomResource primary, Context context) {
var template =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
Service.class,
getClass(),
"/io/javaoperatorsdk/operator/workflow/complexdependent/service.yaml");
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseStatefulSet.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseStatefulSet.java
index b0a7b60805..1e4aa73e80 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseStatefulSet.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/dependent/BaseStatefulSet.java
@@ -19,7 +19,7 @@
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.workflow.complexdependent.ComplexWorkflowCustomResource;
@@ -32,7 +32,7 @@ public BaseStatefulSet(String component) {
protected StatefulSet desired(
ComplexWorkflowCustomResource primary, Context context) {
var template =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
StatefulSet.class,
getClass(),
"/io/javaoperatorsdk/operator/workflow/complexdependent/statefulset.yaml");
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java
index b9aa595b76..e5c7f726f5 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/DeploymentDependentResource.java
@@ -16,7 +16,7 @@
package io.javaoperatorsdk.operator.workflow.workflowallfeature;
import io.fabric8.kubernetes.api.model.apps.Deployment;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource;
@@ -27,7 +27,7 @@ public class DeploymentDependentResource
protected Deployment desired(
WorkflowAllFeatureCustomResource primary, Context context) {
Deployment deployment =
- ReconcilerUtils.loadYaml(
+ ReconcilerUtilsInternal.loadYaml(
Deployment.class,
WorkflowAllFeatureIT.class,
"/io/javaoperatorsdk/operator/nginx-deployment.yaml");
diff --git a/pom.xml b/pom.xml
index 415aa79cbc..67e0f45302 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
pom
Operator SDK for Java
Java SDK for implementing Kubernetes operators
diff --git a/sample-operators/controller-namespace-deletion/pom.xml b/sample-operators/controller-namespace-deletion/pom.xml
index b8994fe20b..ddfeebacbc 100644
--- a/sample-operators/controller-namespace-deletion/pom.xml
+++ b/sample-operators/controller-namespace-deletion/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
sample-operators
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
sample-controller-namespace-deletion
diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml
index 316b926fef..70485a2f3e 100644
--- a/sample-operators/leader-election/pom.xml
+++ b/sample-operators/leader-election/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
sample-operators
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
sample-leader-election
diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml
index 86eb91bee0..a2334ca8c6 100644
--- a/sample-operators/mysql-schema/pom.xml
+++ b/sample-operators/mysql-schema/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
sample-operators
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
sample-mysql-schema-operator
diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml
index 820a110afb..6079d3bb71 100644
--- a/sample-operators/pom.xml
+++ b/sample-operators/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
sample-operators
diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml
index f07760ec28..c9fe8c2d06 100644
--- a/sample-operators/tomcat-operator/pom.xml
+++ b/sample-operators/tomcat-operator/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
sample-operators
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
sample-tomcat-operator
diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java
index 0347b726ac..c4a47069e2 100644
--- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java
+++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java
@@ -18,7 +18,7 @@
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.informer.Informer;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
@@ -36,7 +36,7 @@ private static String tomcatImage(Tomcat tomcat) {
@Override
protected Deployment desired(Tomcat tomcat, Context context) {
Deployment deployment =
- ReconcilerUtils.loadYaml(Deployment.class, getClass(), "deployment.yaml");
+ ReconcilerUtilsInternal.loadYaml(Deployment.class, getClass(), "deployment.yaml");
final ObjectMeta tomcatMetadata = tomcat.getMetadata();
final String tomcatName = tomcatMetadata.getName();
deployment =
diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java
index 72f430528e..bcb0e80026 100644
--- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java
+++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java
@@ -18,7 +18,7 @@
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.informer.Informer;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
@@ -31,7 +31,8 @@ public class ServiceDependentResource extends CRUDKubernetesDependentResource context) {
final ObjectMeta tomcatMetadata = tomcat.getMetadata();
- return new ServiceBuilder(ReconcilerUtils.loadYaml(Service.class, getClass(), "service.yaml"))
+ return new ServiceBuilder(
+ ReconcilerUtilsInternal.loadYaml(Service.class, getClass(), "service.yaml"))
.editMetadata()
.withName(tomcatMetadata.getName())
.withNamespace(tomcatMetadata.getNamespace())
diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml
index 03d363212c..e25920b7da 100644
--- a/sample-operators/webpage/pom.xml
+++ b/sample-operators/webpage/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
sample-operators
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
sample-webpage-operator
diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java
index ab4ed8a337..ecfe66d329 100644
--- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java
+++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java
@@ -21,7 +21,7 @@
import io.javaoperatorsdk.operator.sample.customresource.WebPage;
import io.javaoperatorsdk.operator.sample.customresource.WebPageStatus;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.loadYaml;
public class Utils {
diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java
index 94b460474f..941a159542 100644
--- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java
+++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java
@@ -27,7 +27,7 @@
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
+import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.api.reconciler.Context;
@@ -219,7 +219,8 @@ private boolean match(ConfigMap desiredHtmlConfigMap, ConfigMap existingConfigMa
}
private Service makeDesiredService(WebPage webPage, String ns, Deployment desiredDeployment) {
- Service desiredService = ReconcilerUtils.loadYaml(Service.class, getClass(), "service.yaml");
+ Service desiredService =
+ ReconcilerUtilsInternal.loadYaml(Service.class, getClass(), "service.yaml");
desiredService.getMetadata().setName(serviceName(webPage));
desiredService.getMetadata().setNamespace(ns);
desiredService.getMetadata().setLabels(lowLevelLabel());
@@ -233,7 +234,7 @@ private Service makeDesiredService(WebPage webPage, String ns, Deployment desire
private Deployment makeDesiredDeployment(
WebPage webPage, String deploymentName, String ns, String configMapName) {
Deployment desiredDeployment =
- ReconcilerUtils.loadYaml(Deployment.class, getClass(), "deployment.yaml");
+ ReconcilerUtilsInternal.loadYaml(Deployment.class, getClass(), "deployment.yaml");
desiredDeployment.getMetadata().setName(deploymentName);
desiredDeployment.getMetadata().setNamespace(ns);
desiredDeployment.getMetadata().setLabels(lowLevelLabel());
diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java
index 6d1f7cc911..e383633ab1 100644
--- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java
+++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/DeploymentDependentResource.java
@@ -27,7 +27,7 @@
import io.javaoperatorsdk.operator.sample.Utils;
import io.javaoperatorsdk.operator.sample.customresource.WebPage;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.loadYaml;
import static io.javaoperatorsdk.operator.sample.Utils.configMapName;
import static io.javaoperatorsdk.operator.sample.Utils.deploymentName;
import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR;
diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java
index 3dbc784887..02204d415a 100644
--- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java
+++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/dependentresource/ServiceDependentResource.java
@@ -25,7 +25,7 @@
import io.javaoperatorsdk.operator.sample.Utils;
import io.javaoperatorsdk.operator.sample.customresource.WebPage;
-import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
+import static io.javaoperatorsdk.operator.ReconcilerUtilsInternal.loadYaml;
import static io.javaoperatorsdk.operator.sample.Utils.deploymentName;
import static io.javaoperatorsdk.operator.sample.Utils.serviceName;
import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR;
diff --git a/test-index-processor/pom.xml b/test-index-processor/pom.xml
index 3dad709023..5ea4008f78 100644
--- a/test-index-processor/pom.xml
+++ b/test-index-processor/pom.xml
@@ -22,7 +22,7 @@
io.javaoperatorsdk
java-operator-sdk
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
test-index-processor