From 3db1ccb52abe57ba9064516271ce54ec48f8ff1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 13:33:04 +0100 Subject: [PATCH 01/10] improve: test getting secondary resource directly after filtering caching update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../informer/InformerEventSourceTest.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java index c3a6f8e91e..665888e835 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.client.KubernetesClient; @@ -74,6 +75,8 @@ class InformerEventSourceTest { private final EventHandler eventHandlerMock = mock(EventHandler.class); private final InformerEventSourceConfiguration informerEventSourceConfiguration = mock(InformerEventSourceConfiguration.class); + private SecondaryToPrimaryMapper mockSecondaryToPrimaryMapper = + mock(SecondaryToPrimaryMapper.class); @BeforeEach void setup() { @@ -81,7 +84,7 @@ void setup() { when(informerEventSourceConfiguration.getInformerConfig()).thenReturn(informerConfig); when(informerConfig.getEffectiveNamespaces(any())).thenReturn(DEFAULT_NAMESPACES_SET); when(informerEventSourceConfiguration.getSecondaryToPrimaryMapper()) - .thenReturn(mock(SecondaryToPrimaryMapper.class)); + .thenReturn(mockSecondaryToPrimaryMapper); when(informerEventSourceConfiguration.getResourceClass()).thenReturn(Deployment.class); informerEventSource = @@ -90,6 +93,11 @@ void setup() { // mocking start @Override public synchronized void start() {} + + @Override + public Optional get(ResourceID resourceID) { + return temporaryResourceCache.getResourceFromCache(resourceID); + } }); var mockControllerConfig = mock(ControllerConfiguration.class); @@ -338,6 +346,30 @@ void multipleCachingFilteringUpdates_variant4() { assertNoEventProduced(); } + @Test + void cachingFilteringUpdateEventUpdatesPrimaryToSecondaryIndex() { + withRealTemporaryResourceCache(); + var td = testDeployment(); + when(mockSecondaryToPrimaryMapper.toPrimaryResourceIDs(any())) + .thenReturn(Set.of(ResourceID.fromResource(td))); + + informerEventSource.eventFilteringUpdateAndCacheResource( + td, + d -> { + var d1 = testDeployment(); + d1.getMetadata().setResourceVersion("2"); + return d1; + }); + + var cr = new TestCustomResource(); + cr.setMetadata( + new ObjectMetaBuilder() + .withName(td.getMetadata().getName()) + .withNamespace(td.getMetadata().getNamespace()) + .build()); + assertThat(informerEventSource.getSecondaryResources(cr)).isNotEmpty(); + } + private void assertNoEventProduced() { await() .pollDelay(Duration.ofMillis(50)) From 7b1fb37849d6feb9fd8eb8e1ae9824e9a31475c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 15:20:26 +0100 Subject: [PATCH 02/10] integration test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../CachingFilteringUpdateCustomResource.java | 28 ++++++ .../CachingFilteringUpdateIT.java | 72 ++++++++++++++ .../CachingFilteringUpdateReconciler.java | 95 +++++++++++++++++++ .../CachingFilteringUpdateStatus.java | 29 ++++++ ...java => PreviousAnnotationDisabledIT.java} | 0 5 files changed, 224 insertions(+) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateCustomResource.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateStatus.java rename operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/{ComparableResourceVersionsDisabledIT.java => PreviousAnnotationDisabledIT.java} (100%) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateCustomResource.java new file mode 100644 index 0000000000..0d25bbfdd4 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateCustomResource.java @@ -0,0 +1,28 @@ +/* + * 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.baseapi.cachingfilteringupdate; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +@ShortNames("cfu") +public class CachingFilteringUpdateCustomResource + extends CustomResource implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java new file mode 100644 index 0000000000..42b19549dc --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java @@ -0,0 +1,72 @@ +/* + * 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.baseapi.cachingfilteringupdate; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class CachingFilteringUpdateIT { + + public static final int RESOURCE_NUMBER = 1000; + CachingFilteringUpdateReconciler reconciler = new CachingFilteringUpdateReconciler(); + + @RegisterExtension + LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension.builder() + .withReconciler(new CachingFilteringUpdateReconciler()) + .build(); + + @Test + void testResourceAccessAfterUpdate() { + for (int i = 0; i < RESOURCE_NUMBER; i++) { + operator.create(createCustomResource(i)); + } + await() + .pollDelay(Duration.ofSeconds(5)) + .atMost(Duration.ofSeconds(30)) + .untilAsserted( + () -> { + for (int i = 0; i < RESOURCE_NUMBER; i++) { + if (operator + .getReconcilerOfType(CachingFilteringUpdateReconciler.class) + .isIssueFound()) { + throw new IllegalStateException("Error already found."); + } + var res = operator.get(CachingFilteringUpdateCustomResource.class, "resource" + i); + assertThat(res.getStatus()).isNotNull(); + assertThat(res.getStatus().getUpdated()).isTrue(); + } + }); + } + + public CachingFilteringUpdateCustomResource createCustomResource(int i) { + CachingFilteringUpdateCustomResource resource = new CachingFilteringUpdateCustomResource(); + resource.setMetadata( + new ObjectMetaBuilder() + .withName("resource" + i) + .withNamespace(operator.getNamespace()) + .build()); + return resource; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java new file mode 100644 index 0000000000..1bd60eb2c9 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateReconciler.java @@ -0,0 +1,95 @@ +/* + * 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.baseapi.cachingfilteringupdate; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; + +@ControllerConfiguration +public class CachingFilteringUpdateReconciler + implements Reconciler { + + private final AtomicBoolean issueFound = new AtomicBoolean(false); + + @Override + public UpdateControl reconcile( + CachingFilteringUpdateCustomResource resource, + Context context) { + + context.resourceOperations().serverSideApply(prepareCM(resource)); + var cachedCM = context.getSecondaryResource(ConfigMap.class); + if (cachedCM.isEmpty()) { + issueFound.set(true); + throw new IllegalStateException("Error for resource: " + ResourceID.fromResource(resource)); + } + + ensureStatusExists(resource); + resource.getStatus().setUpdated(true); + return UpdateControl.patchStatus(resource); + } + + private static ConfigMap prepareCM(CachingFilteringUpdateCustomResource p) { + var cm = + new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(p.getMetadata().getName()) + .withNamespace(p.getMetadata().getNamespace()) + .build()) + .withData(Map.of("name", p.getMetadata().getName())) + .build(); + cm.addOwnerReference(p); + return cm; + } + + @Override + public List> prepareEventSources( + EventSourceContext context) { + InformerEventSource cmES = + new InformerEventSource<>( + InformerEventSourceConfiguration.from( + ConfigMap.class, CachingFilteringUpdateCustomResource.class) + .build(), + context); + return List.of(cmES); + } + + private void ensureStatusExists(CachingFilteringUpdateCustomResource resource) { + CachingFilteringUpdateStatus status = resource.getStatus(); + if (status == null) { + status = new CachingFilteringUpdateStatus(); + resource.setStatus(status); + } + } + + public boolean isIssueFound() { + return issueFound.get(); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateStatus.java new file mode 100644 index 0000000000..80b6c4ba54 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateStatus.java @@ -0,0 +1,29 @@ +/* + * 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.baseapi.cachingfilteringupdate; + +public class CachingFilteringUpdateStatus { + + private Boolean updated; + + public Boolean getUpdated() { + return updated; + } + + public void setUpdated(Boolean updated) { + this.updated = updated; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/ComparableResourceVersionsDisabledIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/PreviousAnnotationDisabledIT.java similarity index 100% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/ComparableResourceVersionsDisabledIT.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/PreviousAnnotationDisabledIT.java From ef87160dc7f8a5244732afa38313098184567110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 15:48:50 +0100 Subject: [PATCH 03/10] race condition fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../dependent/workflow/AbstractWorkflowExecutor.java | 4 ++-- .../event/source/informer/ManagedInformerEventSource.java | 7 ++++++- .../operator/sample/MySQLSchemaOperatorE2E.java | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java index 12099ffa25..f032c1a05c 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java @@ -174,10 +174,10 @@ protected void submit( DependentResourceNode dependentResourceNode, NodeExecutor nodeExecutor, String operation) { + logger() + .debug("Submitting to {}: {} primaryID: {}", operation, dependentResourceNode, primaryID); final Future future = executorService.submit(nodeExecutor); markAsExecuting(dependentResourceNode, future); - logger() - .debug("Submitted to {}: {} primaryID: {}", operation, dependentResourceNode, primaryID); } protected void registerOrDeregisterEventSourceBasedOnActivation( 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 38c93d03ae..c2c664839e 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 @@ -183,8 +183,13 @@ public void handleRecentResourceCreate(ResourceID resourceID, R resource) { @Override public Optional get(ResourceID resourceID) { - var res = cache.get(resourceID); + // order of these two resource gets matter, since other way around + // there can be a race condition that we read the resource from the cache + // it is not found. But, right after that we process an event for the informer that + // evicts the temporal resource cache, so at the end resource won't be found in temp cache + // however it is there already in informer cache at that moment. Optional resource = temporaryResourceCache.getResourceFromCache(resourceID); + var res = cache.get(resourceID); if (comparableResourceVersions && resource.isPresent() && res.filter( diff --git a/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java b/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java index 1fe4364250..129b502ed8 100644 --- a/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java +++ b/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java @@ -127,7 +127,7 @@ void test() { .delete(); await() - .atMost(2, MINUTES) + .atMost(4, MINUTES) .ignoreExceptions() .untilAsserted( () -> { From 7ee98ab17730cab4cdc3cfed883510c8308b9291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 15:50:52 +0100 Subject: [PATCH 04/10] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../informer/InformerEventSourceTest.java | 31 +------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java index 665888e835..bf0f91d813 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java @@ -84,7 +84,7 @@ void setup() { when(informerEventSourceConfiguration.getInformerConfig()).thenReturn(informerConfig); when(informerConfig.getEffectiveNamespaces(any())).thenReturn(DEFAULT_NAMESPACES_SET); when(informerEventSourceConfiguration.getSecondaryToPrimaryMapper()) - .thenReturn(mockSecondaryToPrimaryMapper); + .thenReturn(mock(SecondaryToPrimaryMapper.class)); when(informerEventSourceConfiguration.getResourceClass()).thenReturn(Deployment.class); informerEventSource = @@ -93,11 +93,6 @@ void setup() { // mocking start @Override public synchronized void start() {} - - @Override - public Optional get(ResourceID resourceID) { - return temporaryResourceCache.getResourceFromCache(resourceID); - } }); var mockControllerConfig = mock(ControllerConfiguration.class); @@ -346,30 +341,6 @@ void multipleCachingFilteringUpdates_variant4() { assertNoEventProduced(); } - @Test - void cachingFilteringUpdateEventUpdatesPrimaryToSecondaryIndex() { - withRealTemporaryResourceCache(); - var td = testDeployment(); - when(mockSecondaryToPrimaryMapper.toPrimaryResourceIDs(any())) - .thenReturn(Set.of(ResourceID.fromResource(td))); - - informerEventSource.eventFilteringUpdateAndCacheResource( - td, - d -> { - var d1 = testDeployment(); - d1.getMetadata().setResourceVersion("2"); - return d1; - }); - - var cr = new TestCustomResource(); - cr.setMetadata( - new ObjectMetaBuilder() - .withName(td.getMetadata().getName()) - .withNamespace(td.getMetadata().getNamespace()) - .build()); - assertThat(informerEventSource.getSecondaryResources(cr)).isNotEmpty(); - } - private void assertNoEventProduced() { await() .pollDelay(Duration.ofMillis(50)) From ce744da6d02bf5fd083dd6aa66899482f0977a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 15:55:15 +0100 Subject: [PATCH 05/10] format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../event/source/informer/InformerEventSourceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java index bf0f91d813..876bcd0648 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java @@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.client.KubernetesClient; From 13a779fafda2a72b079f916ff3f6fb6d0bcdcbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 15:59:49 +0100 Subject: [PATCH 06/10] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../event/source/informer/InformerEventSourceTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java index 876bcd0648..c3a6f8e91e 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java @@ -74,8 +74,6 @@ class InformerEventSourceTest { private final EventHandler eventHandlerMock = mock(EventHandler.class); private final InformerEventSourceConfiguration informerEventSourceConfiguration = mock(InformerEventSourceConfiguration.class); - private SecondaryToPrimaryMapper mockSecondaryToPrimaryMapper = - mock(SecondaryToPrimaryMapper.class); @BeforeEach void setup() { From ece5807d77efa0619d4a9e3ee07f9b7bd7e1459a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 16:02:40 +0100 Subject: [PATCH 07/10] Update operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../source/informer/ManagedInformerEventSource.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 c2c664839e..016e9b6568 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 @@ -183,11 +183,11 @@ public void handleRecentResourceCreate(ResourceID resourceID, R resource) { @Override public Optional get(ResourceID resourceID) { - // order of these two resource gets matter, since other way around - // there can be a race condition that we read the resource from the cache - // it is not found. But, right after that we process an event for the informer that - // evicts the temporal resource cache, so at the end resource won't be found in temp cache - // however it is there already in informer cache at that moment. + // The order of these two lookups matters. If we queried the informer cache first, + // a race condition could occur: we might not find the resource there yet, then + // process an informer event that evicts the temporary resource cache entry. At that + // point the resource would already be present in the informer cache, but we would + // have missed it in both caches during this call. Optional resource = temporaryResourceCache.getResourceFromCache(resourceID); var res = cache.get(resourceID); if (comparableResourceVersions From 268bb3deea8955b2a1277af2b53f64bb276af2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 16:04:23 +0100 Subject: [PATCH 08/10] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../cachingfilteringupdate/CachingFilteringUpdateIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java index 42b19549dc..79f4c33bf9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java @@ -28,7 +28,7 @@ class CachingFilteringUpdateIT { - public static final int RESOURCE_NUMBER = 1000; + public static final int RESOURCE_NUMBER = 250; CachingFilteringUpdateReconciler reconciler = new CachingFilteringUpdateReconciler(); @RegisterExtension @@ -44,7 +44,7 @@ void testResourceAccessAfterUpdate() { } await() .pollDelay(Duration.ofSeconds(5)) - .atMost(Duration.ofSeconds(30)) + .atMost(Duration.ofMinutes(1)) .untilAsserted( () -> { for (int i = 0; i < RESOURCE_NUMBER; i++) { From b9d0e5a605a6c45a8bec79e76300f923e4ee7c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 16:05:39 +0100 Subject: [PATCH 09/10] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../CachingFilteringUpdateIT.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java index 79f4c33bf9..d1cdb53d29 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java @@ -45,19 +45,35 @@ void testResourceAccessAfterUpdate() { await() .pollDelay(Duration.ofSeconds(5)) .atMost(Duration.ofMinutes(1)) - .untilAsserted( + .until( () -> { - for (int i = 0; i < RESOURCE_NUMBER; i++) { - if (operator - .getReconcilerOfType(CachingFilteringUpdateReconciler.class) - .isIssueFound()) { - throw new IllegalStateException("Error already found."); - } - var res = operator.get(CachingFilteringUpdateCustomResource.class, "resource" + i); - assertThat(res.getStatus()).isNotNull(); - assertThat(res.getStatus().getUpdated()).isTrue(); + if (operator + .getReconcilerOfType(CachingFilteringUpdateReconciler.class) + .isIssueFound()) { + // Stop waiting as soon as an issue is detected. + return true; } + // Use a single representative resource to detect that updates have completed. + var res = + operator.get( + CachingFilteringUpdateCustomResource.class, + "resource" + (RESOURCE_NUMBER - 1)); + return res != null + && res.getStatus() != null + && Boolean.TRUE.equals(res.getStatus().getUpdated()); }); + + if (operator + .getReconcilerOfType(CachingFilteringUpdateReconciler.class) + .isIssueFound()) { + throw new IllegalStateException("Error already found."); + } + + for (int i = 0; i < RESOURCE_NUMBER; i++) { + var res = operator.get(CachingFilteringUpdateCustomResource.class, "resource" + i); + assertThat(res.getStatus()).isNotNull(); + assertThat(res.getStatus().getUpdated()).isTrue(); + } } public CachingFilteringUpdateCustomResource createCustomResource(int i) { From 0664afc65783bc02c60df2545eac18ef911eea66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 28 Feb 2026 16:09:07 +0100 Subject: [PATCH 10/10] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../CachingFilteringUpdateIT.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java index d1cdb53d29..c62c8ca186 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cachingfilteringupdate/CachingFilteringUpdateIT.java @@ -33,9 +33,7 @@ class CachingFilteringUpdateIT { @RegisterExtension LocallyRunOperatorExtension operator = - LocallyRunOperatorExtension.builder() - .withReconciler(new CachingFilteringUpdateReconciler()) - .build(); + LocallyRunOperatorExtension.builder().withReconciler(reconciler).build(); @Test void testResourceAccessAfterUpdate() { @@ -47,9 +45,7 @@ void testResourceAccessAfterUpdate() { .atMost(Duration.ofMinutes(1)) .until( () -> { - if (operator - .getReconcilerOfType(CachingFilteringUpdateReconciler.class) - .isIssueFound()) { + if (reconciler.isIssueFound()) { // Stop waiting as soon as an issue is detected. return true; } @@ -63,9 +59,7 @@ void testResourceAccessAfterUpdate() { && Boolean.TRUE.equals(res.getStatus().getUpdated()); }); - if (operator - .getReconcilerOfType(CachingFilteringUpdateReconciler.class) - .isIssueFound()) { + if (operator.getReconcilerOfType(CachingFilteringUpdateReconciler.class).isIssueFound()) { throw new IllegalStateException("Error already found."); }