improve: filter only own updates for read-after-write-conistency#3414
improve: filter only own updates for read-after-write-conistency#3414csviri wants to merge 38 commits into
Conversation
5dc5f9c to
a18c124
Compare
There was a problem hiding this comment.
Pull request overview
This PR refactors the “read cache after write / filter only own updates” logic into dedicated event-filtering classes and expands coverage with new regression/integration tests for tricky timing scenarios (external updates during own writes, concurrent delete vs status update, etc.) in the operator framework’s informer/controller event pipelines.
Changes:
- Replaces the previous ad-hoc temp-cache filtering logic with
EventFilterSupport/EventFilterWindowand a unifiedGenericResourceEventmodel. - Updates informer/controller event sources to use the new filtering API and adjusts unit tests accordingly.
- Adds new integration tests covering external updates racing with controller updates and deletion racing with status patch.
Reviewed changes
Copilot reviewed 34 out of 34 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pom.xml | Adds a commented Fabric8 client SNAPSHOT override line. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/readownupdates/ReadOwnUpdatesStatus.java | Renames/moves the status model into the new readownupdates package. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/readownupdates/ReadOwnUpdatesReconciler.java | Renames/moves reconciler and adapts types to ReadOwnUpdates* classes. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/readownupdates/ReadOwnUpdatesIT.java | Renames/moves IT and updates references to new reconciler/CR types. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/readownupdates/ReadOwnUpdatesCustomResource.java | Renames/moves CR class and updates short name/type parameters. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/filterpatchevent/FilterPatchEventTestReconciler.java | Moves test reconciler into readcacheafterwrite package and updates static import. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/filterpatchevent/FilterPatchEventTestCustomResourceStatus.java | Moves status class into readcacheafterwrite package. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/filterpatchevent/FilterPatchEventTestCustomResource.java | Moves CR class into readcacheafterwrite package. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/filterpatchevent/FilterPatchEventIT.java | Moves IT into readcacheafterwrite package. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalupdateduringownupdate/ExternalUpdateDuringOwnUpdateStatus.java | New status model for external-update-during-own-update IT. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalupdateduringownupdate/ExternalUpdateDuringOwnUpdateReconciler.java | New reconciler to coordinate a status patch while an external label update occurs. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalupdateduringownupdate/ExternalUpdateDuringOwnUpdateIT.java | New IT validating external updates aren’t incorrectly filtered during own write windows. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalupdateduringownupdate/ExternalUpdateDuringOwnUpdateCustomResource.java | New CR type for the external-update-during-own-update scenario. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalsecondaryupdate/ExternalSecondaryUpdateStatus.java | New status model for external secondary update scenario. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalsecondaryupdate/ExternalSecondaryUpdateReconciler.java | New reconciler exercising secondary SSA updates with external metadata changes between them. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalsecondaryupdate/ExternalSecondaryUpdateIT.java | New IT ensuring external secondary events propagate and are visible via merged caches. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/externalsecondaryupdate/ExternalSecondaryUpdateCustomResource.java | New CR type for the external-secondary-update scenario. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/deletionduringstatusupdate/DeletionDuringStatusUpdateStatus.java | New status model for deletion-during-status-update regression scenario. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/deletionduringstatusupdate/DeletionDuringStatusUpdateReconciler.java | New reconciler that patches status while delete races, to reproduce regression. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/deletionduringstatusupdate/DeletionDuringStatusUpdateIT.java | New IT verifying cleanup is triggered when delete races with a status update. |
| operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/readcacheafterwrite/deletionduringstatusupdate/DeletionDuringStatusUpdateCustomResource.java | New CR type for deletion-during-status-update scenario. |
| operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCacheTest.java | Updates unit tests to new filtering API and adds new regression assertions. |
| operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java | Reworks informer event source tests for the new filtering model and behaviors. |
| operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/EventFilterWindowTest.java | New unit test suite for EventFilterWindow behavior. |
| operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/EventFilterSupportTest.java | New unit tests for EventFilterSupport orchestration and lifecycle. |
| operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSourceTest.java | Updates controller event-source tests to new filtering semantics and expected propagation. |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/TemporaryResourceCache.java | Refactors temp cache to delegate filtering to EventFilterSupport and emit GenericResourceEvent. |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java | Updates the event-filtering update wrapper to use the new GenericResourceEvent path. |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java | Updates add/update/delete handling to work with Optional event emission/defer semantics. |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/GenericResourceEvent.java | Renames/repurposes the prior event type and adds delete “unknown state” support. |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/EventFilterWindow.java | New implementation encapsulating filter-window state + synthesis logic. |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/EventFilterSupport.java | New support class coordinating windows per resource ID. |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/EventFilterDetails.java | Removes the previous EventFilterDetails implementation (replaced by the new classes). |
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerEventSource.java | Updates controller event path to use Optional events from the new temp-cache filter. |
Comments suppressed due to low confidence (2)
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/GenericResourceEvent.java:30
- The field name
lastStateUnknow(and its getter) is misspelled; this becomes part of a public API on apublicclass and will likely be used by callers/tests. Consider renaming tolastStateUnknown(and updating call sites) to avoid locking in the typo.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/GenericResourceEvent.java:71 equalscurrently ignoreslastStateUnknow, andhashCodetherefore can’t reflect that state either. That can cause delete events that differ only bydeletedFinalStateUnknownto be treated as identical (e.g., in tests or de-duplication logic).
| <fabric8-httpclient-impl.name>jdk</fabric8-httpclient-impl.name> | ||
| <junit.version>6.1.0</junit.version> | ||
| <fabric8-client.version>7.7.0</fabric8-client.version> | ||
| <!-- <fabric8-client.version>999-SNAPSHOT</fabric8-client.version>--> |
| public synchronized void setStartingReList(String lastKnownVersion) { | ||
| eventFilterWindows.values().forEach(au -> au.setReListStartedFrom(lastKnownVersion)); | ||
| } |
| var resultEvent = | ||
| temporaryResourceCache.onDeleteEvent(resource, deletedFinalStateUnknown); | ||
| if (resultEvent.isEmpty()) { | ||
| return; | ||
| } |
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
… re-list Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
50260e5 to
f00eba7
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 34 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/GenericResourceEvent.java:49
- Typo in public API:
getLastStateUnknow()should begetLastStateUnknown()(and the underlying field name as well) to match existing terminology likedeletedFinalStateUnknown. Keeping the typo in a public method will be hard to change later.
| <fabric8-httpclient-impl.name>jdk</fabric8-httpclient-impl.name> | ||
| <junit.version>6.1.0</junit.version> | ||
| <fabric8-client.version>7.7.0</fabric8-client.version> | ||
| <!-- <fabric8-client.version>999-SNAPSHOT</fabric8-client.version>--> |
| var resultEvent = | ||
| temporaryResourceCache.onDeleteEvent(resource, deletedFinalStateUnknown); | ||
| if (resultEvent.isEmpty()) { | ||
| return; | ||
| } |
| // @Override | ||
| // public void onBeforeList(String lastSyncResourceVersion) { | ||
| // temporaryResourceCache.setOngoingRelist(lastSyncResourceVersion); | ||
| // } |
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 33 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/GenericResourceEvent.java:49
- Typo in API:
lastStateUnknow/getLastStateUnknow()should belastStateUnknown/getLastStateUnknown(). The misspelling is now part of a public type and is referenced across the codebase, making future cleanup harder.
| var resultEvent = | ||
| temporaryResourceCache.onDeleteEvent(resource, deletedFinalStateUnknown); | ||
| if (resultEvent.isEmpty()) { | ||
| return; | ||
| } | ||
| if (resultEvent.orElseThrow().getAction() != ResourceAction.DELETED) { | ||
| log.warn("Non delete event received on onDelete handling. This should not happen."); | ||
| } | ||
| primaryToSecondaryIndex.onDelete(resource); | ||
| temporaryResourceCache.onDeleteEvent(resource, deletedFinalStateUnknown); | ||
| if (acceptedByDeleteFilters(resource, deletedFinalStateUnknown)) { | ||
| propagateEvent(resource); | ||
| } |
| if (log.isDebugEnabled()) { | ||
| log.debug("Event received with action: {}", action); | ||
| log.trace("Event Old resource: {},\n new resource: {}", oldResource, resource); | ||
| log.debug("Event Old resource: {},\n new resource: {}", oldResource, resource); | ||
| } |
| public synchronized void setOngoingRelist(String lastKnownSyncVersion) { | ||
| eventFilteringSupport.setStartingReList(); | ||
| } |
| public synchronized void setRelistFinished(String syncResourceVersions) { | ||
| // turned off until client support | ||
| // eventFilteringSupport.setRelistFinished(); | ||
| } |
Updated implementation of event filtering for read after write consistency. Now we are filtering only events for our own updates, and always eagerly producing an event that is equivalent to a real life possible event.
The algorithm is now relatively simple and easily unit testable.
The idead is that we just maintain a state and in every place it might produce an event we execute the same check method, see in
EventFilterWindow:java-operator-sdk/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/EventFilterWindow.java
Line 61 in 50260e5
The PR is prepared for re-list callback, but that part is commented out, but tested in
EventFilterSupportTest,EventFilterWindowTest.See: fabric8io/kubernetes-client#7899