From 207c2675271908dd95a3092a35c1bcfa06510bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:03:22 +0100 Subject: [PATCH 01/14] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../en/docs/documentation/reconciler.md | 106 +++++++++++++++--- 1 file changed, 93 insertions(+), 13 deletions(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index a1e3e802a3..cf667e799a 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -160,7 +160,87 @@ annotation. If you do not specify a finalizer name, one will be automatically ge From v5, by default, the finalizer is added using Server Side Apply. See also `UpdateControl` in docs. -### Making sure the primary resource is up to date for the next reconciliation +### Read-cache-after-write consistency and event filtering + +It is an inherent issue with Informers that their caches are eventually consistent even +with the updates to Kubernetes API done from the controller. From version 5.3.0 the framework +supports stronger guarantees, both for primary and secondary resources. If this feature is used: + +1. Reading cache after our update - even withing same reconciliation - returns the fresh resource. + Fresh means at least the version of the resource that is in the response form our update. + Or more recent one if some other party updated the resource after our update. +2. Filtering events for the update from the controller. If the controller updates a resource + the event produced the Kubernetes API and propagated to Informer would normally trigger a next + reconciliation, however this is not ideal, since we already have that up-to-date resource + in the cache in the current reconciliation, so in general it is not desirable to reconcile that again. + This feature makes sure that the reconciliation is not triggered from the event from our writes. + + +In order to have these guarantees use [`ResourceOperations`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java) +from the context of the reconciliation: + +```java + +public UpdateControl reconcile(WebPage webPage, Context context) { + + ConfigMap managedConfigMap = prepareConfigMap(webPage); + // filtering and caching update + context.resourceOperations().serverSideApply(desiredIngress); + + // fresh resource already available from our update in caches + var upToDateResource = context.getSecondaryResource(ConfigMap.class); + + makeStatusChanages(webPage); + + // built in update methods by default uses this approach + return UpdateControl.patchStatus(webPage); +} +``` + +### Built-in patches + +`UpdateControl` and `ErrorStatusUpdateControl` by default uses this functionality. + +Mainly to cover migration path for the cases when somebody expected events for their update +a these controls now contain a new method: `UpdateControl.reschedule()`, that instantly propagates +an event to reschedule the reconciliation. + +### Allocated values + +If you want to store some state - like generated IDs - in `.status` sub-resource, you +can do it safely with this feature. Since it is guaranteed that you will see the update +in the next reconciliation. + +Note that this is not just with `UpdateControl` you can also do update any time on primary resource +with `resourceOperations`: + +```java + +public UpdateControl reconcile(WebPage webPage, Context context) { + + makeStatusChanages(webPage); + // this is equivalent with the update done by UpdateControl + context.resourceOperations().serverSideApplyPrimaryStatus(desiredIngress); + + return UpdateControl.noUpdate(); +} +``` + +### Notes +- Talk about this feature in this [talk](https://www.youtube.com/watch?v=HrwHh5Yh6AM&t=1387s). +- [Umbrella issue](https://github.com/operator-framework/java-operator-sdk/issues/2944) on our GitHub. +- We were able to implement this feature since Kubernetes introduces guideline to compare + resource versions. See the details [here](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5504-comparable-resource-version). + +### Making sure the primary resource is up to date for the next reconciliation (deprecated) + +{{% alert title="Deprecated" %}} + +Read-cache-after-write consistency feature replaces this functionality. + +> It provides this functionality also for secondary resources and optimistic locking + is not required anymore. See details above. +{{% /alert %}} It is typical to want to update the status subresource with the information that is available during the reconciliation. This is sometimes referred to as the last observed state. When the primary resource is updated, though, the framework @@ -263,30 +343,30 @@ See also [sample](https://github.com/operator-framework/java-operator-sdk/blob/m ### Expectations Expectations are a pattern to ensure that, during reconciliation, your secondary resources are in a certain state. -For a more detailed explanation see [this blogpost](https://ahmet.im/blog/controller-pitfalls/#expectations-pattern). -You can find framework support for this pattern in [`io.javaoperatorsdk.operator.processing.expectation`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/) -package. See also related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/ExpectationReconciler.java). -Note that this feature is marked as `@Experimental`, since based on feedback the API might be improved / changed, but we intend -to support it, later also might be integrated to Dependent Resources and/or Workflows. +For a more detailed explanation, see [this blogpost](https://ahmet.im/blog/controller-pitfalls/#expectations-pattern). +You can find framework support for this pattern in the [`io.javaoperatorsdk.operator.processing.expectation`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/) +package. See also the related [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/ExpectationReconciler.java). +Note that this feature is marked as `@Experimental`: based on feedback the API may be improved or changed, but we intend +to keep supporting it and may later integrate it into Dependent Resources and/or Workflows. -The idea is the nutshell, is that you can track your expectations in the expectation manager in the reconciler -which has an API that covers the common use cases. +The idea, in a nutshell, is that you can track your expectations in the expectation manager within the reconciler, +which provides an API covering the common use cases. -The following sample is the simplified version of the integration test that implements the logic that creates a -deployment and sets status message if there are the target three replicas ready: +The following is a simplified version of the integration test that implements the logic to create a +deployment and set a status message once the target three replicas are ready: ```java public class ExpectationReconciler implements Reconciler { // some code is omitted - + private final ExpectationManager expectationManager = new ExpectationManager<>(); @Override public UpdateControl reconcile( ExpectationCustomResource primary, Context context) { - // exiting asap if there is an expectation that is not timed out neither fulfilled yet + // exit early if there is an expectation that has not yet timed out or been fulfilled if (expectationManager.ongoingExpectationPresent(primary, context)) { return UpdateControl.noUpdate(); } @@ -299,7 +379,7 @@ public class ExpectationReconciler implements Reconciler Date: Sun, 1 Mar 2026 20:13:19 +0100 Subject: [PATCH 02/14] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 16 ++++++++++++---- .../docs/documentation/working-with-es-caches.md | 6 ++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index cf667e799a..edd1fdade2 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -169,11 +169,11 @@ supports stronger guarantees, both for primary and secondary resources. If this 1. Reading cache after our update - even withing same reconciliation - returns the fresh resource. Fresh means at least the version of the resource that is in the response form our update. Or more recent one if some other party updated the resource after our update. -2. Filtering events for the update from the controller. If the controller updates a resource - the event produced the Kubernetes API and propagated to Informer would normally trigger a next - reconciliation, however this is not ideal, since we already have that up-to-date resource +2. Filtering events for our updates. If the controller updates a resource + an event is produced by the Kubernetes API and propagated to Informer, what would normally trigger a next + reconciliation. However, this is not ideal, since we already have that up-to-date resource in the cache in the current reconciliation, so in general it is not desirable to reconcile that again. - This feature makes sure that the reconciliation is not triggered from the event from our writes. + This feature also makes sure that the reconciliation is not triggered from the event from our writes. In order to have these guarantees use [`ResourceOperations`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java) @@ -226,6 +226,14 @@ public UpdateControl reconcile(WebPage webPage, Context contex } ``` +### Caveats + +- This feature is implemented on top of fabric8 client informers using additional caches in `InformerEventSource`, + so it is safe to use `context.getSecondaryResources(..)` or `InformerEventSource.get(ResourceID)` + methods. However won't work with `InformerEventSource.list(..)` method, since it directly reads + the underlying informer cache. + + ### Notes - Talk about this feature in this [talk](https://www.youtube.com/watch?v=HrwHh5Yh6AM&t=1387s). - [Umbrella issue](https://github.com/operator-framework/java-operator-sdk/issues/2944) on our GitHub. diff --git a/docs/content/en/docs/documentation/working-with-es-caches.md b/docs/content/en/docs/documentation/working-with-es-caches.md index bb1e140303..a190d2a6cb 100644 --- a/docs/content/en/docs/documentation/working-with-es-caches.md +++ b/docs/content/en/docs/documentation/working-with-es-caches.md @@ -216,3 +216,9 @@ With this index in place, you can retrieve the target resources very efficiently .collect(Collectors.toSet())) .withNamespacesInheritedFromController().build(), context); ``` + +## Read-cache-after-write consistency and event filtering + +From version 5.3.0 we provide stronger consistency guarantees and +other features on top of basic informers, see [this section](reconciler.md#read-cache-after-write-consistency-and-event-filtering) +for details. \ No newline at end of file From 49dd490b369612703cb9e0cba513df30a776d26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:16:52 +0100 Subject: [PATCH 03/14] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index edd1fdade2..c4566457de 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -187,7 +187,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex // filtering and caching update context.resourceOperations().serverSideApply(desiredIngress); - // fresh resource already available from our update in caches + // fresh resource instantly available from our update in the cache var upToDateResource = context.getSecondaryResource(ConfigMap.class); makeStatusChanages(webPage); From 58178e31faf23ff28c4c2cc349f00d4f16cd710e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:18:15 +0100 Subject: [PATCH 04/14] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index c4566457de..4c1e1e4d32 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -219,9 +219,8 @@ with `resourceOperations`: public UpdateControl reconcile(WebPage webPage, Context context) { makeStatusChanages(webPage); - // this is equivalent with the update done by UpdateControl + // this is equivalent to UpdateControl.patchStatus(webpage) context.resourceOperations().serverSideApplyPrimaryStatus(desiredIngress); - return UpdateControl.noUpdate(); } ``` From fec5c0727661c6aa72fd85763bcf8c4d912f3275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:26:22 +0100 Subject: [PATCH 05/14] Update docs/content/en/docs/documentation/reconciler.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index 4c1e1e4d32..a8062ceb0b 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -185,7 +185,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex ConfigMap managedConfigMap = prepareConfigMap(webPage); // filtering and caching update - context.resourceOperations().serverSideApply(desiredIngress); + context.resourceOperations().serverSideApply(managedConfigMap); // fresh resource instantly available from our update in the cache var upToDateResource = context.getSecondaryResource(ConfigMap.class); From aed8bc194cae6ae71da98bf3919eef1a4146b636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:26:35 +0100 Subject: [PATCH 06/14] Update docs/content/en/docs/documentation/reconciler.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index a8062ceb0b..ba2894430a 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -220,7 +220,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex makeStatusChanages(webPage); // this is equivalent to UpdateControl.patchStatus(webpage) - context.resourceOperations().serverSideApplyPrimaryStatus(desiredIngress); + context.resourceOperations().serverSideApplyPrimaryStatus(webPage); return UpdateControl.noUpdate(); } ``` From a6a9fe4780f8e8822171babac840f3090b7dd792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:26:44 +0100 Subject: [PATCH 07/14] Update docs/content/en/docs/documentation/reconciler.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index ba2894430a..b831c825c0 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -166,9 +166,9 @@ It is an inherent issue with Informers that their caches are eventually consiste with the updates to Kubernetes API done from the controller. From version 5.3.0 the framework supports stronger guarantees, both for primary and secondary resources. If this feature is used: -1. Reading cache after our update - even withing same reconciliation - returns the fresh resource. - Fresh means at least the version of the resource that is in the response form our update. - Or more recent one if some other party updated the resource after our update. +1. Reading from the cache after our update — even within the same reconciliation — returns a fresh resource. + "Fresh" means at least the version of the resource that is in the response from our update, + or a more recent version if some other party updated the resource after our update. 2. Filtering events for our updates. If the controller updates a resource an event is produced by the Kubernetes API and propagated to Informer, what would normally trigger a next reconciliation. However, this is not ideal, since we already have that up-to-date resource From ad1fc3cf9b3528833616758002e942eeb9dcb81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:28:07 +0100 Subject: [PATCH 08/14] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index b831c825c0..e90392605b 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -352,7 +352,7 @@ See also [sample](https://github.com/operator-framework/java-operator-sdk/blob/m Expectations are a pattern to ensure that, during reconciliation, your secondary resources are in a certain state. For a more detailed explanation, see [this blogpost](https://ahmet.im/blog/controller-pitfalls/#expectations-pattern). You can find framework support for this pattern in the [`io.javaoperatorsdk.operator.processing.expectation`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/) -package. See also the related [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/ExpectationReconciler.java). +package. See also the related [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/onallevent/ExpectationReconciler.java). Note that this feature is marked as `@Experimental`: based on feedback the API may be improved or changed, but we intend to keep supporting it and may later integrate it into Dependent Resources and/or Workflows. From d9a21ad21b8069144120aa8f64d0bdf49eab7d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:29:36 +0100 Subject: [PATCH 09/14] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/content/en/docs/documentation/reconciler.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index e90392605b..8ca67feabe 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -192,7 +192,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex makeStatusChanages(webPage); - // built in update methods by default uses this approach + // built in update methods by default use this feature return UpdateControl.patchStatus(webPage); } ``` @@ -202,7 +202,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex `UpdateControl` and `ErrorStatusUpdateControl` by default uses this functionality. Mainly to cover migration path for the cases when somebody expected events for their update -a these controls now contain a new method: `UpdateControl.reschedule()`, that instantly propagates +these controls now contain a new method: `UpdateControl.reschedule()`, that instantly propagates an event to reschedule the reconciliation. ### Allocated values From dfdea7b35ed0947d53f1210eea1161303c049ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:39:39 +0100 Subject: [PATCH 10/14] Update docs/content/en/docs/documentation/reconciler.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/content/en/docs/documentation/reconciler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index 8ca67feabe..fa43e708c4 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -218,7 +218,7 @@ with `resourceOperations`: public UpdateControl reconcile(WebPage webPage, Context context) { - makeStatusChanages(webPage); + makeStatusChanges(webPage); // this is equivalent to UpdateControl.patchStatus(webpage) context.resourceOperations().serverSideApplyPrimaryStatus(webPage); return UpdateControl.noUpdate(); From 374cc70036a26debe2e5afb88daa72f68279b78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 20:39:45 +0100 Subject: [PATCH 11/14] Update docs/content/en/docs/documentation/reconciler.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/content/en/docs/documentation/reconciler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index fa43e708c4..4bb253054f 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -190,7 +190,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex // fresh resource instantly available from our update in the cache var upToDateResource = context.getSecondaryResource(ConfigMap.class); - makeStatusChanages(webPage); + makeStatusChanges(webPage); // built in update methods by default use this feature return UpdateControl.patchStatus(webPage); From aa1b679e78ed0b222f8c01bbf7f2f0614a66cf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 1 Mar 2026 21:30:29 +0100 Subject: [PATCH 12/14] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../content/en/blog/news/primary-cache-for-next-recon.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/content/en/blog/news/primary-cache-for-next-recon.md b/docs/content/en/blog/news/primary-cache-for-next-recon.md index 67326a6f17..be84e08a7e 100644 --- a/docs/content/en/blog/news/primary-cache-for-next-recon.md +++ b/docs/content/en/blog/news/primary-cache-for-next-recon.md @@ -5,6 +5,15 @@ author: >- [Attila Mészáros](https://github.com/csviri) and [Chris Laprun](https://github.com/metacosm) --- +{{% alert title="Deprecated" %}} + +Read-cache-after-write consistency feature replaces this functionality. (since version 5.3.0) + +> It provides this functionality also for secondary resources and optimistic locking +is not required anymore. See [details here](./../../docs/documentation/reconciler.md#read-cache-after-write-consistency-and-event-filtering). +{{% /alert %}} + + We recently released v5.1 of Java Operator SDK (JOSDK). One of the highlights of this release is related to a topic of so-called [allocated values](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#representing-allocated-values From 9552d6a774025cfecb8efa26f6b163552cdc8eb6 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Mon, 2 Mar 2026 11:49:10 +0100 Subject: [PATCH 13/14] fix: grammar Co-authored-by: Martin Stefanko --- docs/content/en/docs/documentation/reconciler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index 4bb253054f..fdf02c2e18 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -199,7 +199,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex ### Built-in patches -`UpdateControl` and `ErrorStatusUpdateControl` by default uses this functionality. +`UpdateControl` and `ErrorStatusUpdateControl` by default use this functionality. Mainly to cover migration path for the cases when somebody expected events for their update these controls now contain a new method: `UpdateControl.reschedule()`, that instantly propagates From 08184fa3893f314f4af8f9baefb33a7f34d55002 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Mon, 2 Mar 2026 12:09:28 +0100 Subject: [PATCH 14/14] docs: restructure and improve wording to make things clearer Signed-off-by: Chris Laprun --- .../en/docs/documentation/reconciler.md | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index fdf02c2e18..81af350115 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -168,15 +168,17 @@ supports stronger guarantees, both for primary and secondary resources. If this 1. Reading from the cache after our update — even within the same reconciliation — returns a fresh resource. "Fresh" means at least the version of the resource that is in the response from our update, - or a more recent version if some other party updated the resource after our update. -2. Filtering events for our updates. If the controller updates a resource - an event is produced by the Kubernetes API and propagated to Informer, what would normally trigger a next - reconciliation. However, this is not ideal, since we already have that up-to-date resource - in the cache in the current reconciliation, so in general it is not desirable to reconcile that again. - This feature also makes sure that the reconciliation is not triggered from the event from our writes. + or a more recent version if some other party updated the resource after our update. In particular, this means that + you can safely store state (e.g. generated IDs) in the status sub-resource of your resources since it is now + guaranteed that the stored values will be observable during the next reconciliation. +2. Filtering events for our updates. When a controller updates a resource an event is produced by the Kubernetes API and + propagated to Informer. This would normally trigger another reconciliation. This is, however, not optimal since we + already have that up-to-date resource in the current reconciliation cache. There is generally no need to reconcile + that resource again. This feature also makes sure that the reconciliation is not triggered from the event from our + writes. -In order to have these guarantees use [`ResourceOperations`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java) +In order to benefit from these stronger guarantees, use [`ResourceOperations`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java) from the context of the reconciliation: ```java @@ -197,27 +199,12 @@ public UpdateControl reconcile(WebPage webPage, Context contex } ``` -### Built-in patches - -`UpdateControl` and `ErrorStatusUpdateControl` by default use this functionality. - -Mainly to cover migration path for the cases when somebody expected events for their update -these controls now contain a new method: `UpdateControl.reschedule()`, that instantly propagates -an event to reschedule the reconciliation. - -### Allocated values - -If you want to store some state - like generated IDs - in `.status` sub-resource, you -can do it safely with this feature. Since it is guaranteed that you will see the update -in the next reconciliation. - -Note that this is not just with `UpdateControl` you can also do update any time on primary resource -with `resourceOperations`: +`UpdateControl` and `ErrorStatusUpdateControl` by default use this functionality, but you can also update your primary resource at any time during the reconciliation using `ResourceOperations`: ```java public UpdateControl reconcile(WebPage webPage, Context context) { - + makeStatusChanges(webPage); // this is equivalent to UpdateControl.patchStatus(webpage) context.resourceOperations().serverSideApplyPrimaryStatus(webPage); @@ -225,17 +212,21 @@ public UpdateControl reconcile(WebPage webPage, Context contex } ``` +If your reconciler is built around the assumption that new reconciliations would occur after its own updates, a new +`reschedule` method is provided on `UpdateControl` to immediately reschedule a new reconciliation, to mimic the previous +behavior. + ### Caveats -- This feature is implemented on top of fabric8 client informers using additional caches in `InformerEventSource`, - so it is safe to use `context.getSecondaryResources(..)` or `InformerEventSource.get(ResourceID)` - methods. However won't work with `InformerEventSource.list(..)` method, since it directly reads - the underlying informer cache. +- This feature is implemented on top of the Fabric8 client informers, using additional caches in `InformerEventSource`, + so it is safe to use `context.getSecondaryResources(..)` or `InformerEventSource.get(ResourceID)`methods. Listing + resources directly via `InformerEventSource.list(..)`, however, won't work since this method directly reads from the + underlying informer cache, thus bypassing the additional caches that make the feature possible. ### Notes -- Talk about this feature in this [talk](https://www.youtube.com/watch?v=HrwHh5Yh6AM&t=1387s). -- [Umbrella issue](https://github.com/operator-framework/java-operator-sdk/issues/2944) on our GitHub. +- This [talk](https://www.youtube.com/watch?v=HrwHh5Yh6AM&t=1387s) mentions this feature. +- [Umbrella issue](https://github.com/operator-framework/java-operator-sdk/issues/2944) on GitHub. - We were able to implement this feature since Kubernetes introduces guideline to compare resource versions. See the details [here](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5504-comparable-resource-version). @@ -245,8 +236,7 @@ public UpdateControl reconcile(WebPage webPage, Context contex Read-cache-after-write consistency feature replaces this functionality. -> It provides this functionality also for secondary resources and optimistic locking - is not required anymore. See details above. +> It provides this functionality also for secondary resources and optimistic locking is not required anymore. See details above. {{% /alert %}} It is typical to want to update the status subresource with the information that is available during the reconciliation.