From 578b5e79487eec3f9415efcd555d8a087ae30693 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Thu, 30 Oct 2025 08:22:09 -0400 Subject: [PATCH 01/18] Annotation removal using locking (#3015) Signed-off-by: Steve Hawkins --- .../operator/api/config/ConfigurationService.java | 2 ++ 1 file changed, 2 insertions(+) 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 6ed9b7ff64..90b8ebaf7b 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 @@ -44,6 +44,8 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; +import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_COMPARABLE_RESOURCE_VERSIONS; + /** An interface from which to retrieve configuration information. */ public interface ConfigurationService { From f2dfd4850281b16be70d74a1d42ed06b1bbc35c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Thu, 13 Nov 2025 04:43:22 +0100 Subject: [PATCH 02/18] improve: complete comparable resource version configs (#3027) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/api/config/ConfigurationService.java | 2 -- 1 file changed, 2 deletions(-) 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 90b8ebaf7b..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 @@ -44,8 +44,6 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; -import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_COMPARABLE_RESOURCE_VERSIONS; - /** An interface from which to retrieve configuration information. */ public interface ConfigurationService { From 4328a4ecf801cceb1a98849d9efd69ab7c40097d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 14 Oct 2025 17:29:36 +0200 Subject: [PATCH 03/18] chore: change version to 5.2.0-SNAPSHOT (#2995) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- bootstrapper-maven-plugin/pom.xml | 2 +- micrometer-support/pom.xml | 2 +- operator-framework-bom/pom.xml | 2 +- operator-framework-core/pom.xml | 2 +- operator-framework-junit5/pom.xml | 2 +- operator-framework/pom.xml | 2 +- sample-operators/leader-election/pom.xml | 2 +- sample-operators/mysql-schema/pom.xml | 2 +- sample-operators/pom.xml | 2 +- sample-operators/tomcat-operator/pom.xml | 2 +- sample-operators/webpage/pom.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml index 364495fca9..0ae72b2ff3 100644 --- a/bootstrapper-maven-plugin/pom.xml +++ b/bootstrapper-maven-plugin/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT bootstrapper diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index 93765e4a8a..319eac7453 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT micrometer-support diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 1ffa1701f8..010934a7cc 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk operator-framework-bom - 5.3.0-SNAPSHOT + 5.2.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 361bb3ad53..7dfd862ddc 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT ../pom.xml diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 60c235a9ec..4992f9d6bc 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT operator-framework-junit-5 diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 253907eb1e..031f903ee0 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT operator-framework diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml index 70485a2f3e..85658a13a6 100644 --- a/sample-operators/leader-election/pom.xml +++ b/sample-operators/leader-election/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT sample-leader-election diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml index a2334ca8c6..5ace3c48a8 100644 --- a/sample-operators/mysql-schema/pom.xml +++ b/sample-operators/mysql-schema/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT sample-mysql-schema-operator diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index 6079d3bb71..4ce07ce912 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT sample-operators diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index c9fe8c2d06..15eab9585d 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT sample-tomcat-operator diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index e25920b7da..7976d0fc7b 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.3.0-SNAPSHOT + 5.2.0-SNAPSHOT sample-webpage-operator From ba30d9bea0e564696ca3534977f95bd9a6cf750d Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Thu, 30 Oct 2025 08:22:09 -0400 Subject: [PATCH 04/18] Annotation removal using locking (#3015) Signed-off-by: Steve Hawkins --- .../operator/api/config/ConfigurationService.java | 2 ++ 1 file changed, 2 insertions(+) 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 6ed9b7ff64..90b8ebaf7b 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 @@ -44,6 +44,8 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; +import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_COMPARABLE_RESOURCE_VERSIONS; + /** An interface from which to retrieve configuration information. */ public interface ConfigurationService { From 906ac34fc8c580d65b8784c78c79380301b3bcee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 1 Nov 2025 00:54:45 +0100 Subject: [PATCH 05/18] feat: comparable resource version utils --- .../io/javaoperatorsdk/operator/Operator.java | 2 +- ...tils.java => ReconcilerUtilsInternal.java} | 4 +- .../config/AbstractConfigurationService.java | 4 +- .../api/config/BaseConfigurationService.java | 4 +- .../api/config/ControllerConfiguration.java | 12 +- .../informer/InformerConfiguration.java | 4 +- .../api/reconciler/ReconcilerUtils.java | 124 ++++++++++++++++++ .../KubernetesDependentResource.java | 2 - .../event/ReconciliationDispatcher.java | 19 +-- .../controller/ControllerEventSource.java | 2 +- .../source/informer/InformerEventSource.java | 23 +--- .../source/informer/InformerManager.java | 4 +- .../source/informer/InformerWrapper.java | 6 +- .../informer/ManagedInformerEventSource.java | 16 +++ .../javaoperatorsdk/operator/OperatorIT.java | 2 +- ....java => ReconcilerUtilsInternalTest.java} | 28 ++-- .../GenericKubernetesResourceMatcherTest.java | 4 +- .../GenericResourceUpdaterTest.java | 4 +- ...dGenericKubernetesResourceMatcherTest.java | 4 +- .../controller/ControllerEventSourceTest.java | 4 +- .../junit/LocallyRunOperatorExtension.java | 10 +- .../baseapi/LeaderElectionPermissionIT.java | 6 +- .../BuiltInResourceCleanerIT.java | 2 +- .../InfrastructureClientIT.java | 12 +- .../baseapi/simple/TestReconciler.java | 4 +- .../config/BaseConfigurationServiceTest.java | 6 +- .../DefaultConfigurationServiceTest.java | 4 +- .../ExternalStateReconciler.java | 4 +- .../InformerRelatedBehaviorITS.java | 14 +- .../ServiceDependentResource.java | 2 +- .../ServiceDependentResource.java | 2 +- .../StandaloneDependentTestReconciler.java | 4 +- ...lSetDesiredSanitizerDependentResource.java | 4 +- .../dependent/BaseService.java | 4 +- .../dependent/BaseStatefulSet.java | 4 +- .../DeploymentDependentResource.java | 4 +- .../sample/DeploymentDependentResource.java | 4 +- .../sample/ServiceDependentResource.java | 5 +- .../operator/sample/Utils.java | 2 +- .../operator/sample/WebPageReconciler.java | 7 +- .../DeploymentDependentResource.java | 2 +- .../ServiceDependentResource.java | 2 +- 42 files changed, 251 insertions(+), 129 deletions(-) rename operator-framework-core/src/main/java/io/javaoperatorsdk/operator/{ReconcilerUtils.java => ReconcilerUtilsInternal.java} (99%) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java rename operator-framework-core/src/test/java/io/javaoperatorsdk/operator/{ReconcilerUtilsTest.java => ReconcilerUtilsInternalTest.java} (84%) 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/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/InformerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java index 30a1a32e8a..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; @@ -92,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"}) 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..48a1bb46b9 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java @@ -0,0 +1,124 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.util.function.UnaryOperator; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; +import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; + +public class ReconcilerUtils { + // toto namespace handling + // todo compare resource version if multiple event sources provide the same resource + // for json patch make sense to retry (json merge patch?) + + public static R ssa(Context context, R resource) { + return handleResourcePatch( + context, + resource, + r -> + context + .getClient() + .resource(r) + .patch( + new PatchContext.Builder() + .withForce(true) + .withFieldManager(context.getControllerConfiguration().fieldManager()) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build())); + } + + public static R ssaStatus( + Context context, R resource) { + return handleResourcePatch( + 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())); + } + + public static

P ssaPrimary(Context

context, P resource) { + return handleResourcePatch( + resource, + r -> + context + .getClient() + .resource(r) + .patch( + new PatchContext.Builder() + .withForce(true) + .withFieldManager(context.getControllerConfiguration().fieldManager()) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build()), + true, + context.eventSourceRetriever().getControllerEventSource()); + } + + public static

P ssaStatusPrimary(Context

context, P resource) { + return handleResourcePatch( + resource, + r -> + context + .getClient() + .resource(r) + .subresource("status") + .patch( + new PatchContext.Builder() + .withForce(true) + .withFieldManager(context.getControllerConfiguration().fieldManager()) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build()), + true, + context.eventSourceRetriever().getControllerEventSource()); + } + + public static R handleResourcePatch( + Context context, R resource, UnaryOperator updateOperation) { + 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 handleResourcePatch(resource, updateOperation, true, mes); + } else { + throw new IllegalStateException( + "Target event source must be a subclass off " + + ManagedInformerEventSource.class.getName()); + } + } + + @SuppressWarnings("unchecked") + private static R handleResourcePatch( + R resource, + UnaryOperator updateOperation, + boolean doNotLock, + ManagedInformerEventSource ies) { + var resourceVersion = resource.getMetadata().getResourceVersion(); + try { + if (resourceVersion != null && doNotLock) { + resource.getMetadata().setResourceVersion(null); + } + return (R) ies.updateAndCacheResource(resource, updateOperation); + } finally { + if (resourceVersion != null && doNotLock) { + resource.getMetadata().setResourceVersion(resourceVersion); + } + } + } +} 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 5d53b807cc..846e3f5ceb 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 @@ -77,7 +77,6 @@ protected R handleCreate(R desired, P primary, Context

context) { .orElseThrow() .updateAndCacheResource( desired, - context, toCreate -> KubernetesDependentResource.super.handleCreate(toCreate, primary, context)); } @@ -87,7 +86,6 @@ protected R handleUpdate(R actual, R desired, P primary, Context

context) { .orElseThrow() .updateAndCacheResource( desired, - context, toUpdate -> KubernetesDependentResource.super.handleUpdate(actual, toUpdate, primary, context)); } 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..9f5c8d93d4 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 @@ -33,13 +33,14 @@ 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.ReconcilerUtils; import io.javaoperatorsdk.operator.api.reconciler.RetryInfo; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.Controller; @@ -203,7 +204,7 @@ private PostExecutionControl

reconcileExecution( } if (updateControl.isPatchStatus()) { - customResourceFacade.patchStatus(toUpdate, originalResource); + customResourceFacade.patchStatus(context, toUpdate, originalResource); } return createPostExecutionControl(updatedCustomResource, updateControl, executionScope); } @@ -241,7 +242,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; @@ -529,20 +530,14 @@ public R patchResource(R resource, R originalResource) { } } - 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 ReconcilerUtils.ssaStatusPrimary(context, resource); } finally { resource.getMetadata().setManagedFields(managedFields); } @@ -562,7 +557,7 @@ private R editStatus(R resource, R originalResource) { var res = resource(clonedOriginal); return res.editStatus( r -> { - ReconcilerUtils.setStatus(r, ReconcilerUtils.getStatus(resource)); + ReconcilerUtilsInternal.setStatus(r, ReconcilerUtilsInternal.getStatus(resource)); return r; }); } finally { 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 f7ed9fdc8e..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.*; 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 0feb3dc2a8..fa0dc9502d 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.function.UnaryOperator; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -98,22 +97,6 @@ private InformerEventSource( genericFilter = informerConfig.getGenericFilter(); } - public R updateAndCacheResource( - R resourceToUpdate, Context context, 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); - } - } - @Override public void onAdd(R newResource) { if (log.isDebugEnabled()) { @@ -233,15 +216,15 @@ public Set getSecondaryResources(P primary) { @Override public void handleRecentResourceUpdate( ResourceID resourceID, R resource, R previousVersionOfResource) { - handleRecentCreateOrUpdate(Operation.UPDATE, resource, previousVersionOfResource); + handleRecentCreateOrUpdate(resource); } @Override public void handleRecentResourceCreate(ResourceID resourceID, R resource) { - handleRecentCreateOrUpdate(Operation.ADD, resource, null); + handleRecentCreateOrUpdate(resource); } - private void handleRecentCreateOrUpdate(Operation operation, R newResource, R oldResource) { + private void handleRecentCreateOrUpdate(R newResource) { primaryToSecondaryIndex.onAddOrUpdate(newResource); temporaryResourceCache.putResource(newResource); } 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 c3a4a9f2c1..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 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 af30617d92..230c649872 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; @@ -97,6 +98,21 @@ public void changeNamespaces(Set namespaces) { } } + public R updateAndCacheResource(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() { 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/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java index 8a920b28b9..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 @@ -26,7 +26,7 @@ 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; @@ -198,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/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-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 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 crClass) { - applyCrd(ReconcilerUtils.getResourceTypeName(crClass)); + applyCrd(ReconcilerUtilsInternal.getResourceTypeName(crClass)); } /** @@ -203,7 +203,7 @@ public void applyCrd(Class 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/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..812007708a 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 @@ -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/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/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 89d1dee94b..cc574fcbe1 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 @@ -110,9 +110,7 @@ private void createExternalResource( // This is critical in this case, since on next reconciliation if it would not be in the cache // it would be created again. configMapEventSource.updateAndCacheResource( - configMap, - context, - toCreate -> context.getClient().configMaps().resource(toCreate).create()); + configMap, toCreate -> context.getClient().configMaps().resource(toCreate).create()); externalResourceEventSource.handleRecentResourceCreate(primaryID, createdResource); } 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/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/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; From 34de531f0a8b697c9a43a31f4ef9b9308e1c9804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 16 Nov 2025 19:20:43 +0100 Subject: [PATCH 06/18] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/reconciler/ReconcileUtils.java | 179 ++++++++++++++++++ .../api/reconciler/ReconcilerUtils.java | 124 ------------ .../event/ReconciliationDispatcher.java | 22 +-- .../event/ReconciliationDispatcherTest.java | 45 ++--- 4 files changed, 213 insertions(+), 157 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtils.java delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java 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..6cd6a4db69 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtils.java @@ -0,0 +1,179 @@ +/* + * 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.util.function.UnaryOperator; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; +import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; + +public class ReconcileUtils { + + private ReconcileUtils() {} + + // todo javadoc + // todo move finalizers mtehods & deprecate + // todo namespace handling + // todo compare resource version if multiple event sources provide the same resource + // for json patch make sense to retry for ? + + public static R serverSideApply( + Context context, R resource) { + 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())); + } + + public static R serverSideApplyStatus( + Context context, R resource) { + 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())); + } + + public static

P serverSideApplyPrimary(Context

context, P resource) { + 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()); + } + + public static

P serverSideApplyPrimaryStatus( + Context

context, P resource) { + 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()); + } + + public static R update( + Context context, R resource) { + return resourcePatch(context, resource, r -> context.getClient().resource(r).update()); + } + + public static R updateStatus( + Context context, R resource) { + return resourcePatch(context, resource, r -> context.getClient().resource(r).updateStatus()); + } + + public static R jsonPatch( + Context context, R resource, UnaryOperator unaryOperator) { + return resourcePatch( + context, resource, r -> context.getClient().resource(r).edit(unaryOperator)); + } + + public static R jsonPatchStatus( + Context context, R resource, UnaryOperator unaryOperator) { + return resourcePatch( + context, resource, r -> context.getClient().resource(r).editStatus(unaryOperator)); + } + + public static R jsonPatchPrimary( + Context context, R resource, UnaryOperator unaryOperator) { + return resourcePatch( + resource, + r -> context.getClient().resource(r).edit(unaryOperator), + context.eventSourceRetriever().getControllerEventSource()); + } + + public static R jsonPatchPrimaryStatus( + Context context, R resource, UnaryOperator unaryOperator) { + return resourcePatch( + resource, + r -> context.getClient().resource(r).editStatus(unaryOperator), + context.eventSourceRetriever().getControllerEventSource()); + } + + public static R jsonMergePatch( + Context context, R resource) { + return resourcePatch(context, resource, r -> context.getClient().resource(r).patch()); + } + + public static R jsonMergePatchStatus( + Context context, R resource) { + return resourcePatch(context, resource, r -> context.getClient().resource(r).patchStatus()); + } + + public static R resourcePatch( + Context context, R resource, UnaryOperator updateOperation) { + 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); + } else { + throw new IllegalStateException( + "Target event source must be a subclass off " + + ManagedInformerEventSource.class.getName()); + } + } + + @SuppressWarnings("unchecked") + public static R resourcePatch( + R resource, UnaryOperator updateOperation, ManagedInformerEventSource ies) { + return (R) ies.updateAndCacheResource(resource, updateOperation); + } +} 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 deleted file mode 100644 index 48a1bb46b9..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java +++ /dev/null @@ -1,124 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler; - -import java.util.function.UnaryOperator; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.dsl.base.PatchContext; -import io.fabric8.kubernetes.client.dsl.base.PatchType; -import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; - -public class ReconcilerUtils { - // toto namespace handling - // todo compare resource version if multiple event sources provide the same resource - // for json patch make sense to retry (json merge patch?) - - public static R ssa(Context context, R resource) { - return handleResourcePatch( - context, - resource, - r -> - context - .getClient() - .resource(r) - .patch( - new PatchContext.Builder() - .withForce(true) - .withFieldManager(context.getControllerConfiguration().fieldManager()) - .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build())); - } - - public static R ssaStatus( - Context context, R resource) { - return handleResourcePatch( - 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())); - } - - public static

P ssaPrimary(Context

context, P resource) { - return handleResourcePatch( - resource, - r -> - context - .getClient() - .resource(r) - .patch( - new PatchContext.Builder() - .withForce(true) - .withFieldManager(context.getControllerConfiguration().fieldManager()) - .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build()), - true, - context.eventSourceRetriever().getControllerEventSource()); - } - - public static

P ssaStatusPrimary(Context

context, P resource) { - return handleResourcePatch( - resource, - r -> - context - .getClient() - .resource(r) - .subresource("status") - .patch( - new PatchContext.Builder() - .withForce(true) - .withFieldManager(context.getControllerConfiguration().fieldManager()) - .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build()), - true, - context.eventSourceRetriever().getControllerEventSource()); - } - - public static R handleResourcePatch( - Context context, R resource, UnaryOperator updateOperation) { - 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 handleResourcePatch(resource, updateOperation, true, mes); - } else { - throw new IllegalStateException( - "Target event source must be a subclass off " - + ManagedInformerEventSource.class.getName()); - } - } - - @SuppressWarnings("unchecked") - private static R handleResourcePatch( - R resource, - UnaryOperator updateOperation, - boolean doNotLock, - ManagedInformerEventSource ies) { - var resourceVersion = resource.getMetadata().getResourceVersion(); - try { - if (resourceVersion != null && doNotLock) { - resource.getMetadata().setResourceVersion(null); - } - return (R) ies.updateAndCacheResource(resource, updateOperation); - } finally { - if (resourceVersion != null && doNotLock) { - resource.getMetadata().setResourceVersion(resourceVersion); - } - } - } -} 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 9f5c8d93d4..84fc5b0823 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 @@ -40,7 +40,7 @@ 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.ReconcilerUtils; +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; @@ -195,7 +195,7 @@ private PostExecutionControl

reconcileExecution( } if (updateControl.isPatchResource()) { - updatedCustomResource = patchResource(toUpdate, originalResource); + updatedCustomResource = patchResource(context, toUpdate, originalResource); if (!useSSA) { toUpdate .getMetadata() @@ -381,7 +381,7 @@ private P addFinalizerWithSSA(P originalResource) { objectMeta.setNamespace(originalResource.getMetadata().getNamespace()); resource.setMetadata(objectMeta); resource.addFinalizer(configuration().getFinalizerName()); - return customResourceFacade.patchResourceWithSSA(resource); + return customResourceFacade.simpleServerSideApply(resource); } catch (InstantiationException | IllegalAccessException | InvocationTargetException @@ -406,7 +406,7 @@ private P updateCustomResourceWithFinalizer(P resourceForExecution, P originalRe 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), @@ -419,7 +419,7 @@ 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() { @@ -444,7 +444,7 @@ public P conflictRetryingPatch( if (forceNotUseSSA) { return customResourceFacade.patchResourceWithoutSSA(resource, originalResource); } else { - return customResourceFacade.patchResource(resource, originalResource); + return customResourceFacade.simpleServerSideApply(resource); } } catch (KubernetesClientException e) { log.trace("Exception during patch for resource: {}", resource); @@ -516,7 +516,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: {}", @@ -524,9 +524,9 @@ 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); } } @@ -537,7 +537,7 @@ public R patchStatus(Context context, R resource, R originalResource) { try { resource.getMetadata().setManagedFields(null); var res = resource(resource); - return ReconcilerUtils.ssaStatusPrimary(context, resource); + return ReconcileUtils.serverSideApplyPrimaryStatus(context, resource); } finally { resource.getMetadata().setManagedFields(managedFields); } @@ -567,7 +567,7 @@ private R editStatus(R resource, R originalResource) { } } - public R patchResourceWithSSA(R resource) { + public R simpleServerSideApply(R resource) { return resource(resource) .patch( new PatchContext.Builder() 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..c804ad4361 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 @@ -158,7 +158,7 @@ void addFinalizerOnNewResource() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); verify(reconciler, never()).reconcile(ArgumentMatchers.eq(testCustomResource), any()); verify(customResourceFacade, times(1)) - .patchResourceWithSSA( + .simpleServerSideApply( argThat(testCustomResource -> testCustomResource.hasFinalizer(DEFAULT_FINALIZER))); } @@ -173,6 +173,7 @@ void addFinalizerOnNewResourceWithoutSSA() { verify(reconciler, never()).reconcile(ArgumentMatchers.eq(testCustomResource), any()); verify(customResourceFacade, times(1)) .patchResource( + any(), argThat(testCustomResource -> testCustomResource.hasFinalizer(DEFAULT_FINALIZER)), any()); assertThat(testCustomResource.hasFinalizer(DEFAULT_FINALIZER)).isTrue(); @@ -190,13 +191,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 +208,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 @@ -354,7 +355,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,21 +365,21 @@ 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); + when(customResourceFacade.simpleServerSideApply(any())).thenReturn(testCustomResource); var postExecControl = reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); verify(customResourceFacade, times(1)) - .patchResourceWithSSA(argThat(a -> !a.getMetadata().getFinalizers().isEmpty())); + .simpleServerSideApply(argThat(a -> !a.getMetadata().getFinalizers().isEmpty())); assertThat(postExecControl.updateIsStatusPatch()).isFalse(); assertThat(postExecControl.getUpdatedCustomResource()).isPresent(); } @@ -390,7 +391,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 +472,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 +490,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 +530,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 +551,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 +574,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 +596,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 +612,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()); } @@ -667,7 +668,7 @@ void retriesAddingFinalizerWithoutSSA() { removeFinalizers(testCustomResource); reconciler.reconcile = (r, c) -> UpdateControl.noUpdate(); - when(customResourceFacade.patchResource(any(), any())) + when(customResourceFacade.patchResource(any(), any(), any())) .thenThrow(new KubernetesClientException(null, 409, null)) .thenReturn(testCustomResource); when(customResourceFacade.getResource(any(), any())) @@ -680,7 +681,7 @@ void retriesAddingFinalizerWithoutSSA() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - verify(customResourceFacade, times(2)).patchResource(any(), any()); + verify(customResourceFacade, times(2)).patchResource(any(), any(), any()); } @Test From 957b37a8162ca25ff0d742d4a81a7febfb96a7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 17 Nov 2025 14:39:01 +0100 Subject: [PATCH 07/18] fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../processing/event/ReconciliationDispatcher.java | 2 +- .../event/ReconciliationDispatcherTest.java | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) 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 84fc5b0823..241986962e 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 @@ -403,7 +403,7 @@ private P updateCustomResourceWithFinalizer(P resourceForExecution, P originalRe resourceForExecution, originalResource, r -> r.addFinalizer(configuration().getFinalizerName()), - false); + true); } private P patchResource(Context

context, P resource, P originalResource) { 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 c804ad4361..07c7164028 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 @@ -164,7 +164,7 @@ void addFinalizerOnNewResource() { @Test void addFinalizerOnNewResourceWithoutSSA() { - initConfigService(false); + initConfigService(false, false); final ReconciliationDispatcher dispatcher = init(testCustomResource, reconciler, null, customResourceFacade, true); @@ -172,11 +172,9 @@ void addFinalizerOnNewResourceWithoutSSA() { dispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); verify(reconciler, never()).reconcile(ArgumentMatchers.eq(testCustomResource), any()); verify(customResourceFacade, times(1)) - .patchResource( - any(), - argThat(testCustomResource -> testCustomResource.hasFinalizer(DEFAULT_FINALIZER)), - any()); - assertThat(testCustomResource.hasFinalizer(DEFAULT_FINALIZER)).isTrue(); + .patchResourceWithoutSSA( + argThat(cr -> cr.hasFinalizer(DEFAULT_FINALIZER)), + argThat(cr -> !cr.hasFinalizer(DEFAULT_FINALIZER))); } @Test @@ -668,7 +666,7 @@ void retriesAddingFinalizerWithoutSSA() { removeFinalizers(testCustomResource); reconciler.reconcile = (r, c) -> UpdateControl.noUpdate(); - when(customResourceFacade.patchResource(any(), any(), any())) + when(customResourceFacade.patchResourceWithoutSSA(any(), any())) .thenThrow(new KubernetesClientException(null, 409, null)) .thenReturn(testCustomResource); when(customResourceFacade.getResource(any(), any())) @@ -681,7 +679,7 @@ void retriesAddingFinalizerWithoutSSA() { reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - verify(customResourceFacade, times(2)).patchResource(any(), any(), any()); + verify(customResourceFacade, times(2)).patchResourceWithoutSSA(any(), any()); } @Test From 2da91655036eae1f4a85e05f35f9bb8adb2ac657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Dec 2025 11:33:09 +0100 Subject: [PATCH 08/18] fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/api/config/ConfigurationService.java | 2 -- .../builtinresourcecleaner/BuiltInResourceCleanerIT.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) 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 90b8ebaf7b..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 @@ -44,8 +44,6 @@ import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory; import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; -import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_COMPARABLE_RESOURCE_VERSIONS; - /** An interface from which to retrieve configuration information. */ public interface ConfigurationService { 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 812007708a..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; From cf2a96d526a4f009e4b58d656787fc838daa56dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Dec 2025 11:36:31 +0100 Subject: [PATCH 09/18] wip --- bootstrapper-maven-plugin/pom.xml | 2 +- micrometer-support/pom.xml | 2 +- operator-framework-bom/pom.xml | 2 +- operator-framework-core/pom.xml | 2 +- operator-framework-junit5/pom.xml | 2 +- operator-framework/pom.xml | 2 +- sample-operators/leader-election/pom.xml | 2 +- sample-operators/mysql-schema/pom.xml | 2 +- sample-operators/pom.xml | 2 +- sample-operators/tomcat-operator/pom.xml | 2 +- sample-operators/webpage/pom.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml index 0ae72b2ff3..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.0-SNAPSHOT + 5.3.0-SNAPSHOT bootstrapper diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index 319eac7453..93765e4a8a 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.0-SNAPSHOT + 5.3.0-SNAPSHOT micrometer-support diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 010934a7cc..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.0-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 7dfd862ddc..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.0-SNAPSHOT + 5.3.0-SNAPSHOT ../pom.xml diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 4992f9d6bc..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.0-SNAPSHOT + 5.3.0-SNAPSHOT operator-framework-junit-5 diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 031f903ee0..253907eb1e 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.0-SNAPSHOT + 5.3.0-SNAPSHOT operator-framework diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml index 85658a13a6..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.0-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 5ace3c48a8..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.0-SNAPSHOT + 5.3.0-SNAPSHOT sample-mysql-schema-operator diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index 4ce07ce912..6079d3bb71 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.0-SNAPSHOT + 5.3.0-SNAPSHOT sample-operators diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index 15eab9585d..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.0-SNAPSHOT + 5.3.0-SNAPSHOT sample-tomcat-operator diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index 7976d0fc7b..e25920b7da 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.2.0-SNAPSHOT + 5.3.0-SNAPSHOT sample-webpage-operator From 5da6a2a4ba111fc956551609dc48625368a05193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Dec 2025 13:45:32 +0100 Subject: [PATCH 10/18] wip --- .../api/reconciler/ReconcileUtils.java | 182 ++++++++++++++++-- .../KubernetesDependentResource.java | 4 +- .../informer/ManagedInformerEventSource.java | 7 + .../ExternalStateReconciler.java | 2 +- 4 files changed, 174 insertions(+), 21 deletions(-) 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 index 6cd6a4db69..4bb988b563 100644 --- 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 @@ -26,14 +26,18 @@ public class ReconcileUtils { private ReconcileUtils() {} - // todo javadoc // todo move finalizers mtehods & deprecate // todo namespace handling // todo compare resource version if multiple event sources provide the same resource - // for json patch make sense to retry for ? + // todo for json patch make sense to retry for ? public static R serverSideApply( Context context, R resource) { + return serverSideApply(context, resource, true); + } + + public static R serverSideApply( + Context context, R resource, boolean filterEvent) { return resourcePatch( context, resource, @@ -46,11 +50,17 @@ public static R serverSideApply( .withForce(true) .withFieldManager(context.getControllerConfiguration().fieldManager()) .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build())); + .build()), + filterEvent); } public static R serverSideApplyStatus( Context context, R resource) { + return serverSideApplyStatus(context, resource, true); + } + + public static R serverSideApplyStatus( + Context context, R resource, boolean filterEvent) { return resourcePatch( context, resource, @@ -64,10 +74,16 @@ public static R serverSideApplyStatus( .withForce(true) .withFieldManager(context.getControllerConfiguration().fieldManager()) .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build())); + .build()), + filterEvent); } public static

P serverSideApplyPrimary(Context

context, P resource) { + return serverSideApplyPrimary(context, resource, true); + } + + public static

P serverSideApplyPrimary( + Context

context, P resource, boolean filterEvent) { return resourcePatch( resource, r -> @@ -80,11 +96,17 @@ public static

P serverSideApplyPrimary(Context

contex .withFieldManager(context.getControllerConfiguration().fieldManager()) .withPatchType(PatchType.SERVER_SIDE_APPLY) .build()), - context.eventSourceRetriever().getControllerEventSource()); + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); } public static

P serverSideApplyPrimaryStatus( Context

context, P resource) { + return serverSideApplyPrimaryStatus(context, resource, true); + } + + public static

P serverSideApplyPrimaryStatus( + Context

context, P resource, boolean filterEvent) { return resourcePatch( resource, r -> @@ -98,59 +120,178 @@ public static

P serverSideApplyPrimaryStatus( .withFieldManager(context.getControllerConfiguration().fieldManager()) .withPatchType(PatchType.SERVER_SIDE_APPLY) .build()), - context.eventSourceRetriever().getControllerEventSource()); + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); } public static R update( Context context, R resource) { - return resourcePatch(context, resource, r -> context.getClient().resource(r).update()); + return update(context, resource, true); + } + + public static R update( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + context, resource, r -> context.getClient().resource(r).update(), filterEvent); } public static R updateStatus( Context context, R resource) { - return resourcePatch(context, resource, r -> context.getClient().resource(r).updateStatus()); + return updateStatus(context, resource, true); + } + + public static R updateStatus( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + context, resource, r -> context.getClient().resource(r).updateStatus(), filterEvent); + } + + public static R updatePrimary( + Context context, R resource) { + return updatePrimary(context, resource, true); + } + + public static R updatePrimary( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + resource, + r -> context.getClient().resource(r).update(), + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); + } + + public static R updatePrimaryStatus( + Context context, R resource) { + return updatePrimaryStatus(context, resource, true); + } + + public static R updatePrimaryStatus( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + resource, + r -> context.getClient().resource(r).updateStatus(), + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); } public static R jsonPatch( Context context, R resource, UnaryOperator unaryOperator) { + return jsonPatch(context, resource, unaryOperator, true); + } + + public static R jsonPatch( + Context context, + R resource, + UnaryOperator unaryOperator, + boolean filterEvent) { return resourcePatch( - context, resource, r -> context.getClient().resource(r).edit(unaryOperator)); + context, resource, r -> context.getClient().resource(r).edit(unaryOperator), filterEvent); } public static R jsonPatchStatus( Context context, R resource, UnaryOperator unaryOperator) { + return jsonPatchStatus(context, resource, unaryOperator, true); + } + + public static R jsonPatchStatus( + Context context, + R resource, + UnaryOperator unaryOperator, + boolean filterEvent) { return resourcePatch( - context, resource, r -> context.getClient().resource(r).editStatus(unaryOperator)); + context, + resource, + r -> context.getClient().resource(r).editStatus(unaryOperator), + filterEvent); } public static R jsonPatchPrimary( Context context, R resource, UnaryOperator unaryOperator) { + return jsonPatchPrimary(context, resource, unaryOperator, true); + } + + public static R jsonPatchPrimary( + Context context, + R resource, + UnaryOperator unaryOperator, + boolean filterEvent) { return resourcePatch( resource, r -> context.getClient().resource(r).edit(unaryOperator), - context.eventSourceRetriever().getControllerEventSource()); + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); } public static R jsonPatchPrimaryStatus( Context context, R resource, UnaryOperator unaryOperator) { + return jsonPatchPrimaryStatus(context, resource, unaryOperator, true); + } + + public static R jsonPatchPrimaryStatus( + Context context, + R resource, + UnaryOperator unaryOperator, + boolean filterEvent) { return resourcePatch( resource, r -> context.getClient().resource(r).editStatus(unaryOperator), - context.eventSourceRetriever().getControllerEventSource()); + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); } public static R jsonMergePatch( Context context, R resource) { - return resourcePatch(context, resource, r -> context.getClient().resource(r).patch()); + return jsonMergePatch(context, resource, true); + } + + public static R jsonMergePatch( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + context, resource, r -> context.getClient().resource(r).patch(), filterEvent); + } + + public static R jsonMergePatchStatus( + Context context, R resource) { + return jsonMergePatchStatus(context, resource, true); } public static R jsonMergePatchStatus( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + context, resource, r -> context.getClient().resource(r).patchStatus(), filterEvent); + } + + public static R jsonMergePatchPrimary( + Context context, R resource) { + return jsonMergePatchPrimary(context, resource, true); + } + + public static R jsonMergePatchPrimary( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + resource, + r -> context.getClient().resource(r).patch(), + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); + } + + public static R jsonMergePatchPrimaryStatus( Context context, R resource) { - return resourcePatch(context, resource, r -> context.getClient().resource(r).patchStatus()); + return jsonMergePatchPrimaryStatus(context, resource, true); + } + + public static R jsonMergePatchPrimaryStatus( + Context context, R resource, boolean filterEvent) { + return resourcePatch( + resource, + r -> context.getClient().resource(r).patch(), + context.eventSourceRetriever().getControllerEventSource(), + filterEvent); } public static R resourcePatch( - Context context, R resource, UnaryOperator updateOperation) { + 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()); @@ -163,7 +304,7 @@ public static R resourcePatch( } var es = esList.get(0); if (es instanceof ManagedInformerEventSource mes) { - return resourcePatch(resource, updateOperation, mes); + return resourcePatch(resource, updateOperation, mes, filterEvent); } else { throw new IllegalStateException( "Target event source must be a subclass off " @@ -173,7 +314,12 @@ public static R resourcePatch( @SuppressWarnings("unchecked") public static R resourcePatch( - R resource, UnaryOperator updateOperation, ManagedInformerEventSource ies) { - return (R) ies.updateAndCacheResource(resource, updateOperation); + R resource, + UnaryOperator updateOperation, + ManagedInformerEventSource ies, + boolean filterEvent) { + return filterEvent + ? (R) ies.eventFilteringUpdateAndCacheResource(resource, updateOperation) + : (R) ies.updateAndCacheResource(resource, updateOperation); } } 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 846e3f5ceb..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 @@ -75,7 +75,7 @@ public void configureWith(KubernetesDependentResourceConfig config) { protected R handleCreate(R desired, P primary, Context

context) { return eventSource() .orElseThrow() - .updateAndCacheResource( + .eventFilteringUpdateAndCacheResource( desired, toCreate -> KubernetesDependentResource.super.handleCreate(toCreate, primary, context)); } @@ -84,7 +84,7 @@ protected R handleCreate(R desired, P primary, Context

context) { protected R handleUpdate(R actual, R desired, P primary, Context

context) { return eventSource() .orElseThrow() - .updateAndCacheResource( + .eventFilteringUpdateAndCacheResource( desired, toUpdate -> KubernetesDependentResource.super.handleUpdate(actual, toUpdate, primary, context)); 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 230c649872..a45dbd8f74 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 @@ -99,6 +99,13 @@ 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); 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 cc574fcbe1..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 @@ -109,7 +109,7 @@ private void createExternalResource( // 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.updateAndCacheResource( + configMapEventSource.eventFilteringUpdateAndCacheResource( configMap, toCreate -> context.getClient().configMaps().resource(toCreate).create()); externalResourceEventSource.handleRecentResourceCreate(primaryID, createdResource); } From 643191f0dc66f7a45bbd32f27c53fbca7a4829e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Dec 2025 14:59:07 +0100 Subject: [PATCH 11/18] wip --- .../PrimaryUpdateAndCacheUtils.java | 41 --- .../api/reconciler/ReconcileUtils.java | 249 +++++++++++++++++- .../event/ReconciliationDispatcher.java | 144 +--------- .../informer/ManagedInformerEventSource.java | 7 +- .../informer/TemporaryResourceCache.java | 8 +- .../PrimaryUpdateAndCacheUtilsTest.java | 51 ---- .../api/reconciler/ReconcileUtilsTest.java | 79 ++++++ .../event/ReconciliationDispatcherTest.java | 115 ++++---- 8 files changed, 387 insertions(+), 307 deletions(-) create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtilsTest.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java index 11dfd21648..6103b4b12b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java @@ -450,45 +450,4 @@ public static

P addFinalizerWithSSA( e); } } - - public static int compareResourceVersions(HasMetadata h1, HasMetadata h2) { - return compareResourceVersions( - h1.getMetadata().getResourceVersion(), h2.getMetadata().getResourceVersion()); - } - - 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/ReconcileUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtils.java index 4bb988b563..0e94b87aaf 100644 --- 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 @@ -15,19 +15,36 @@ */ 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 namespace handling + // todo test namespace handling // todo compare resource version if multiple event sources provide the same resource // todo for json patch make sense to retry for ? @@ -322,4 +339,234 @@ public static R resourcePatch( ? (R) ies.eventFilteringUpdateAndCacheResource(resource, updateOperation) : (R) ies.updateAndCacheResource(resource, updateOperation); } + + public static

P addFinalizer(Context

context) { + return addFinalizer(context, context.getControllerConfiguration().getFinalizerName()); + } + + /** + * Adds finalizer to the primary resource from the context 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 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 finalizer) { + return addFinalizer(context.getClient(), context.getPrimaryResource(), finalizer); + } + + /** + * 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( + KubernetesClient client, P resource, String finalizerName) { + if (resource.isMarkedForDeletion() || resource.hasFinalizer(finalizerName)) { + return resource; + } + return conflictRetryingPatch( + client, + resource, + r -> { + r.addFinalizer(finalizerName); + return r; + }, + r -> !r.hasFinalizer(finalizerName)); + } + + public static

P removeFinalizer(Context

context) { + return removeFinalizer(context, context.getControllerConfiguration().getFinalizerName()); + } + + /** + * Removes the target finalizer from the primary resource from the Context. 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) { + return removeFinalizer(context.getClient(), context.getPrimaryResource(), finalizerName); + } + + /** + * 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( + KubernetesClient client, P resource, String finalizerName) { + if (!resource.hasFinalizer(finalizerName)) { + return resource; + } + return conflictRetryingPatch( + client, + resource, + 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 client KubernetesClient + * @param resource to update + * @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") + public static

P conflictRetryingPatch( + KubernetesClient client, + P resource, + UnaryOperator

resourceChangesOperator, + Predicate

preCondition) { + 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 client.resource(resource).edit(resourceChangesOperator); + } 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(); + } + } + } + } + + 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. + * + * @return the patched resource from the server response + */ + 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); + } 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); + } + } + + public static int compareResourceVersions(HasMetadata h1, HasMetadata h2) { + return compareResourceVersions( + h1.getMetadata().getResourceVersion(), h2.getMetadata().getResourceVersion()); + } + + 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/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java index 241986962e..c06093f271 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,12 +24,9 @@ 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.ReconcilerUtilsInternal; import io.javaoperatorsdk.operator.api.config.Cloner; @@ -120,7 +115,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); } @@ -149,9 +144,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 { @@ -318,10 +313,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: {}", @@ -335,24 +327,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); } } @@ -368,44 +343,6 @@ 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.simpleServerSideApply(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()), - true); - } - private P patchResource(Context

context, P resource, P originalResource) { log.debug( "Updating resource: {} with version: {}; SSA: {}", @@ -426,57 +363,6 @@ 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.simpleServerSideApply(resource); - } - } 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())) { @@ -536,17 +422,16 @@ public R patchStatus(Context context, R resource, R originalResource) { var managedFields = resource.getMetadata().getManagedFields(); try { resource.getMetadata().setManagedFields(null); - var res = resource(resource); 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. @@ -554,8 +439,9 @@ 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, + originalResource, r -> { ReconcilerUtilsInternal.setStatus(r, ReconcilerUtilsInternal.getStatus(resource)); return r; @@ -567,16 +453,6 @@ private R editStatus(R resource, R originalResource) { } } - public R simpleServerSideApply(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/informer/ManagedInformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java index a45dbd8f74..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 @@ -35,7 +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.PrimaryUpdateAndCacheUtils; +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; @@ -160,10 +160,7 @@ public Optional get(ResourceID resourceID) { Optional resource = temporaryResourceCache.getResourceFromCache(resourceID); if (comparableResourceVersions && resource.isPresent() - && res.filter( - r -> - PrimaryUpdateAndCacheUtils.compareResourceVersions(r, resource.orElseThrow()) - > 0) + && res.filter(r -> ReconcileUtils.compareResourceVersions(r, resource.orElseThrow()) > 0) .isEmpty()) { log.debug("Latest resource found in temporary cache for Resource ID: {}", resourceID); return resource; 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 d918be447d..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 @@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils; +import io.javaoperatorsdk.operator.api.reconciler.ReconcileUtils; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; @@ -117,7 +117,7 @@ private boolean onEvent(T resource, boolean unknownState) { (id, cached) -> { boolean remove = unknownState; if (!unknownState) { - int comp = PrimaryUpdateAndCacheUtils.compareResourceVersions(resource, cached); + int comp = ReconcileUtils.compareResourceVersions(resource, cached); if (comp >= 0) { remove = true; } @@ -157,7 +157,7 @@ public synchronized void putResource(T newResource) { // this also prevents resurrecting recently deleted entities for which the delete event // has already been processed if (latestResourceVersion != null - && PrimaryUpdateAndCacheUtils.compareResourceVersions( + && ReconcileUtils.compareResourceVersions( latestResourceVersion, newResource.getMetadata().getResourceVersion()) > 0) { log.debug( @@ -172,7 +172,7 @@ public synchronized void putResource(T newResource) { var cachedResource = getResourceFromCache(resourceId).orElse(null); if (cachedResource == null - || PrimaryUpdateAndCacheUtils.compareResourceVersions(newResource, cachedResource) > 0) { + || ReconcileUtils.compareResourceVersions(newResource, cachedResource) > 0) { log.debug( "Temporarily moving ahead to target version {} for resource id: {}", newResource.getMetadata().getResourceVersion(), diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java index c878a4fc06..235dd3cd40 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java @@ -19,7 +19,6 @@ import java.util.function.UnaryOperator; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +39,6 @@ import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; import static io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils.DEFAULT_MAX_RETRY; -import static io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils.compareResourceVersions; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -182,53 +180,4 @@ void cachePollTimeouts() { 10L)); assertThat(ex.getMessage()).contains("Timeout"); } - - @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/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/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java index 07c7164028..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,27 +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)) - .simpleServerSideApply( - 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, 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)) - .patchResourceWithoutSSA( - argThat(cr -> cr.hasFinalizer(DEFAULT_FINALIZER)), - argThat(cr -> !cr.hasFinalizer(DEFAULT_FINALIZER))); + 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 @@ -231,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); @@ -260,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 @@ -278,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 = @@ -369,17 +339,19 @@ void doesNotUpdateTheResourceIfNoUpdateUpdateControlIfFinalizerSet() { @Test void addsFinalizerIfNotMarkedForDeletionAndEmptyCustomResourceReturned() { - removeFinalizers(testCustomResource); - reconciler.reconcile = (r, c) -> UpdateControl.noUpdate(); - when(customResourceFacade.simpleServerSideApply(any())).thenReturn(testCustomResource); - - var postExecControl = - reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource)); - - verify(customResourceFacade, times(1)) - .simpleServerSideApply(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 @@ -659,6 +631,7 @@ void canSkipSchedulingMaxDelayIf() { } @Test + @Disabled("Move to reconciler utils test") void retriesAddingFinalizerWithoutSSA() { initConfigService(false); reconciliationDispatcher = From b9bddab355fdad236fde8936b3f60f91d533382b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Dec 2025 15:50:03 +0100 Subject: [PATCH 12/18] wip --- .../api/reconciler/ReconcileUtils.java | 493 ++++++++++++++++-- 1 file changed, 454 insertions(+), 39 deletions(-) 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 index 0e94b87aaf..8c240fabf7 100644 --- 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 @@ -46,13 +46,40 @@ private ReconcileUtils() {} // todo move finalizers mtehods & deprecate // todo test namespace handling // todo compare resource version if multiple event sources provide the same resource - // todo for json patch make sense to retry for ? + /** + * 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 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 context, R resource, boolean filterEvent) { return resourcePatch( @@ -71,11 +98,33 @@ public static R serverSideApply( 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 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 context, R resource, boolean filterEvent) { return resourcePatch( @@ -95,10 +144,33 @@ public static R serverSideApplyStatus( 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( @@ -117,11 +189,33 @@ public static

P serverSideApplyPrimary( 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( @@ -141,33 +235,97 @@ public static

P serverSideApplyPrimaryStatus( 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 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 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 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 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 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 context, R resource, boolean filterEvent) { return resourcePatch( @@ -177,11 +335,33 @@ public static R updatePrimary( 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 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 context, R resource, boolean filterEvent) { return resourcePatch( @@ -191,11 +371,35 @@ public static R updatePrimaryStatus( 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 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 context, R resource, @@ -205,11 +409,35 @@ public static R jsonPatch( 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 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 context, R resource, @@ -222,11 +450,35 @@ public static R jsonPatchStatus( 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 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 context, R resource, @@ -239,11 +491,35 @@ public static R jsonPatchPrimary( 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 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 context, R resource, @@ -256,33 +532,100 @@ public static R jsonPatchPrimaryStatus( 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 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 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 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 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 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 context, R resource, boolean filterEvent) { return resourcePatch( @@ -292,11 +635,33 @@ public static R jsonMergePatchPrimary( 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 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 context, R resource, boolean filterEvent) { return resourcePatch( @@ -306,6 +671,19 @@ public static R jsonMergePatchPrimaryStatus( 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) { @@ -329,6 +707,18 @@ public static R resourcePatch( } } + /** + * 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, @@ -340,21 +730,18 @@ public static R resourcePatch( : (R) ies.updateAndCacheResource(resource, updateOperation); } - public static

P addFinalizer(Context

context) { - return addFinalizer(context, context.getControllerConfiguration().getFinalizerName()); - } - /** - * Adds finalizer to the primary resource from the context 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 add finalizer if there is already a finalizer or - * resource is marked for deletion. + * 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, String finalizer) { - return addFinalizer(context.getClient(), context.getPrimaryResource(), finalizer); + public static

P addFinalizer(Context

context) { + return addFinalizer(context, context.getControllerConfiguration().getFinalizerName()); } /** @@ -365,14 +752,13 @@ public static

P addFinalizer(Context

context, String * * @return updated resource from the server response */ - public static

P addFinalizer( - KubernetesClient client, P resource, String finalizerName) { + public static

P addFinalizer(Context

context, String finalizerName) { + var resource = context.getPrimaryResource(); if (resource.isMarkedForDeletion() || resource.hasFinalizer(finalizerName)) { return resource; } return conflictRetryingPatch( - client, - resource, + context, r -> { r.addFinalizer(finalizerName); return r; @@ -380,21 +766,18 @@ public static

P addFinalizer( r -> !r.hasFinalizer(finalizerName)); } - public static

P removeFinalizer(Context

context) { - return removeFinalizer(context, context.getControllerConfiguration().getFinalizerName()); - } - /** - * Removes the target finalizer from the primary resource from the Context. 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. + * 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, String finalizerName) { - return removeFinalizer(context.getClient(), context.getPrimaryResource(), finalizerName); + public static

P removeFinalizer(Context

context) { + return removeFinalizer(context, context.getControllerConfiguration().getFinalizerName()); } /** @@ -406,13 +789,13 @@ public static

P removeFinalizer( * @return updated resource from the server response */ public static

P removeFinalizer( - KubernetesClient client, P resource, String finalizerName) { + Context

context, String finalizerName) { + var resource = context.getPrimaryResource(); if (!resource.hasFinalizer(finalizerName)) { return resource; } return conflictRetryingPatch( - client, - resource, + context, r -> { r.removeFinalizer(finalizerName); return r; @@ -425,8 +808,7 @@ public static

P removeFinalizer( * unprocessable content (HTTP 422) it retries the operation up to the maximum number defined in * {@link PrimaryUpdateAndCacheUtils#DEFAULT_MAX_RETRY}. * - * @param client KubernetesClient - * @param resource to update + * @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. @@ -434,11 +816,10 @@ public static

P removeFinalizer( * @param

resource type */ @SuppressWarnings("unchecked") - public static

P conflictRetryingPatch( - KubernetesClient client, - P resource, - UnaryOperator

resourceChangesOperator, - Predicate

preCondition) { + 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)); } @@ -448,7 +829,7 @@ public static

P conflictRetryingPatch( if (!preCondition.test(resource)) { return resource; } - return client.resource(resource).edit(resourceChangesOperator); + return jsonPatchPrimary(context, resource, resourceChangesOperator, false); } catch (KubernetesClientException e) { log.trace("Exception during patch for resource: {}", resource); retryIndex++; @@ -488,6 +869,16 @@ public static

P conflictRetryingPatch( } } + /** + * 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()); } @@ -497,7 +888,10 @@ public static

P addFinalizerWithSSA(Context

context) * 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) { @@ -516,7 +910,7 @@ public static

P addFinalizerWithSSA( resource.setMetadata(objectMeta); resource.addFinalizer(finalizerName); - return serverSideApplyPrimary(context, resource); + return serverSideApplyPrimary(context, resource, false); } catch (InstantiationException | IllegalAccessException | InvocationTargetException @@ -529,11 +923,32 @@ public static

P addFinalizerWithSSA( } } + /** + * 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); From a1f943cd4796ac529369691cca0764b4c2778fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Dec 2025 16:08:56 +0100 Subject: [PATCH 13/18] wip --- .../operator/processing/event/ReconciliationDispatcher.java | 4 +--- .../PatchResourceAndStatusNoSSAReconciler.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) 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 c06093f271..556362c6c1 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 @@ -377,7 +377,6 @@ static class CustomResourceFacade { private final MixedOperation, Resource> resourceOperation; private final boolean useSSA; - private final String fieldManager; private final Cloner cloner; public CustomResourceFacade( @@ -386,7 +385,6 @@ public CustomResourceFacade( Cloner cloner) { this.resourceOperation = resourceOperation; this.useSSA = configuration.getConfigurationService().useSSAToPatchPrimaryResource(); - this.fieldManager = configuration.fieldManager(); this.cloner = cloner; } @@ -441,7 +439,7 @@ private R editStatus(Context context, R resource, R originalResource) { resource.getMetadata().setResourceVersion(null); return ReconcileUtils.jsonPatchPrimaryStatus( context, - originalResource, + clonedOriginal, r -> { ReconcilerUtilsInternal.setStatus(r, ReconcilerUtilsInternal.getStatus(resource)); return r; 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); From 9ecb404af7422ea54fc7bf31094d92471f8e00af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Dec 2025 16:18:37 +0100 Subject: [PATCH 14/18] wip --- .../processing/event/source/informer/InformerEventSource.java | 1 - 1 file changed, 1 deletion(-) 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 fa0dc9502d..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 @@ -27,7 +27,6 @@ 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; From d36eb311e252582087b6259773f94fccc0a1cdf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 1 Nov 2025 00:54:45 +0100 Subject: [PATCH 15/18] feat: comparable resource version utils --- .../api/reconciler/ReconcilerUtils.java | 124 ++++++++++++++++++ .../event/ReconciliationDispatcher.java | 5 +- 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java 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..48a1bb46b9 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconcilerUtils.java @@ -0,0 +1,124 @@ +package io.javaoperatorsdk.operator.api.reconciler; + +import java.util.function.UnaryOperator; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; +import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; + +public class ReconcilerUtils { + // toto namespace handling + // todo compare resource version if multiple event sources provide the same resource + // for json patch make sense to retry (json merge patch?) + + public static R ssa(Context context, R resource) { + return handleResourcePatch( + context, + resource, + r -> + context + .getClient() + .resource(r) + .patch( + new PatchContext.Builder() + .withForce(true) + .withFieldManager(context.getControllerConfiguration().fieldManager()) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build())); + } + + public static R ssaStatus( + Context context, R resource) { + return handleResourcePatch( + 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())); + } + + public static

P ssaPrimary(Context

context, P resource) { + return handleResourcePatch( + resource, + r -> + context + .getClient() + .resource(r) + .patch( + new PatchContext.Builder() + .withForce(true) + .withFieldManager(context.getControllerConfiguration().fieldManager()) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build()), + true, + context.eventSourceRetriever().getControllerEventSource()); + } + + public static

P ssaStatusPrimary(Context

context, P resource) { + return handleResourcePatch( + resource, + r -> + context + .getClient() + .resource(r) + .subresource("status") + .patch( + new PatchContext.Builder() + .withForce(true) + .withFieldManager(context.getControllerConfiguration().fieldManager()) + .withPatchType(PatchType.SERVER_SIDE_APPLY) + .build()), + true, + context.eventSourceRetriever().getControllerEventSource()); + } + + public static R handleResourcePatch( + Context context, R resource, UnaryOperator updateOperation) { + 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 handleResourcePatch(resource, updateOperation, true, mes); + } else { + throw new IllegalStateException( + "Target event source must be a subclass off " + + ManagedInformerEventSource.class.getName()); + } + } + + @SuppressWarnings("unchecked") + private static R handleResourcePatch( + R resource, + UnaryOperator updateOperation, + boolean doNotLock, + ManagedInformerEventSource ies) { + var resourceVersion = resource.getMetadata().getResourceVersion(); + try { + if (resourceVersion != null && doNotLock) { + resource.getMetadata().setResourceVersion(null); + } + return (R) ies.updateAndCacheResource(resource, updateOperation); + } finally { + if (resourceVersion != null && doNotLock) { + resource.getMetadata().setResourceVersion(resourceVersion); + } + } + } +} 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 556362c6c1..5b52f3f9b6 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 @@ -35,7 +35,7 @@ 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.ReconcilerUtils; import io.javaoperatorsdk.operator.api.reconciler.RetryInfo; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.Controller; @@ -420,7 +420,8 @@ public R patchStatus(Context context, R resource, R originalResource) { var managedFields = resource.getMetadata().getManagedFields(); try { resource.getMetadata().setManagedFields(null); - return ReconcileUtils.serverSideApplyPrimaryStatus(context, resource); + var res = resource(resource); + return ReconcilerUtils.ssaStatusPrimary(context, resource); } finally { resource.getMetadata().setManagedFields(managedFields); } From 84ea5d04d913acac48e0325175f142f8c0135397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 3 Dec 2025 16:05:22 +0100 Subject: [PATCH 16/18] merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../api/reconciler/ReconcilerUtils.java | 139 ++---------------- .../event/ReconciliationDispatcher.java | 5 +- 2 files changed, 17 insertions(+), 127 deletions(-) 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 index 48a1bb46b9..3e1a4f9b14 100644 --- 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 @@ -1,124 +1,15 @@ -package io.javaoperatorsdk.operator.api.reconciler; - -import java.util.function.UnaryOperator; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.dsl.base.PatchContext; -import io.fabric8.kubernetes.client.dsl.base.PatchType; -import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource; - -public class ReconcilerUtils { - // toto namespace handling - // todo compare resource version if multiple event sources provide the same resource - // for json patch make sense to retry (json merge patch?) - - public static R ssa(Context context, R resource) { - return handleResourcePatch( - context, - resource, - r -> - context - .getClient() - .resource(r) - .patch( - new PatchContext.Builder() - .withForce(true) - .withFieldManager(context.getControllerConfiguration().fieldManager()) - .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build())); - } - - public static R ssaStatus( - Context context, R resource) { - return handleResourcePatch( - 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())); - } - - public static

P ssaPrimary(Context

context, P resource) { - return handleResourcePatch( - resource, - r -> - context - .getClient() - .resource(r) - .patch( - new PatchContext.Builder() - .withForce(true) - .withFieldManager(context.getControllerConfiguration().fieldManager()) - .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build()), - true, - context.eventSourceRetriever().getControllerEventSource()); - } - - public static

P ssaStatusPrimary(Context

context, P resource) { - return handleResourcePatch( - resource, - r -> - context - .getClient() - .resource(r) - .subresource("status") - .patch( - new PatchContext.Builder() - .withForce(true) - .withFieldManager(context.getControllerConfiguration().fieldManager()) - .withPatchType(PatchType.SERVER_SIDE_APPLY) - .build()), - true, - context.eventSourceRetriever().getControllerEventSource()); - } - - public static R handleResourcePatch( - Context context, R resource, UnaryOperator updateOperation) { - 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 handleResourcePatch(resource, updateOperation, true, mes); - } else { - throw new IllegalStateException( - "Target event source must be a subclass off " - + ManagedInformerEventSource.class.getName()); - } - } - - @SuppressWarnings("unchecked") - private static R handleResourcePatch( - R resource, - UnaryOperator updateOperation, - boolean doNotLock, - ManagedInformerEventSource ies) { - var resourceVersion = resource.getMetadata().getResourceVersion(); - try { - if (resourceVersion != null && doNotLock) { - resource.getMetadata().setResourceVersion(null); - } - return (R) ies.updateAndCacheResource(resource, updateOperation); - } finally { - if (resourceVersion != null && doNotLock) { - resource.getMetadata().setResourceVersion(resourceVersion); - } - } - } -} +/* + * 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/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java index 5b52f3f9b6..556362c6c1 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 @@ -35,7 +35,7 @@ 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.ReconcilerUtils; +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; @@ -420,8 +420,7 @@ public R patchStatus(Context context, R resource, R originalResource) { var managedFields = resource.getMetadata().getManagedFields(); try { resource.getMetadata().setManagedFields(null); - var res = resource(resource); - return ReconcilerUtils.ssaStatusPrimary(context, resource); + return ReconcileUtils.serverSideApplyPrimaryStatus(context, resource); } finally { resource.getMetadata().setManagedFields(managedFields); } From 50617df3c28f412e2ede1a162191bef955e16f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 3 Dec 2025 16:06:16 +0100 Subject: [PATCH 17/18] wip --- .../operator/processing/event/ReconciliationDispatcher.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 556362c6c1..f0ac07f38f 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 @@ -44,9 +44,7 @@ /** 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; From 9e305d3cb26fc2f8814b8753ad68c2863a43c286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 3 Dec 2025 16:06:21 +0100 Subject: [PATCH 18/18] wip --- .../operator/processing/event/ReconciliationDispatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f0ac07f38f..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 @@ -44,7 +44,7 @@ /** Handles calls and results of a Reconciler and finalizer related logic */ class ReconciliationDispatcher

{ - + private static final Logger log = LoggerFactory.getLogger(ReconciliationDispatcher.class); private final Controller

controller;