Skip to content

Commit 0630f4f

Browse files
committed
test fix
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent cb7bb01 commit 0630f4f

2 files changed

Lines changed: 96 additions & 4 deletions

File tree

docs/content/en/blog/releases/v5-5-release.md

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,98 @@ author: >-
55
[Attila Mészáros](https://github.com/csviri)
66
---
77

8-
We're pleased to announce the release of Java Operator SDK v5.5.0!
8+
We're pleased to announce the release of Java Operator SDK v5.5.0! This minor version reworks how
9+
the framework filters the events caused by a controller's own writes, making it more correct and
10+
more efficient, and exposes a richer, matcher-aware `ResourceOperations` API. There are **no
11+
breaking API changes**, but there is one behavioral change around `UpdateControl` — see the
12+
migration notes.
913

1014
## Key Features
1115

16+
### Matcher-based updates in `ResourceOperations`
17+
18+
`ResourceOperations` (available from the reconciliation `Context` via `context.resourceOperations()`)
19+
now offers a complete, consistent family of update/patch/create methods for every write strategy —
20+
**server-side apply, update (PUT), JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7386)** — each
21+
available for the whole resource and the `status` subresource, and with dedicated `primary`
22+
variants.
23+
24+
Every method comes in two flavors: a default one, and one taking an `Options` argument that controls
25+
how the resulting own event is handled:
26+
27+
```java
28+
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
29+
makeStatusChanges(webPage);
30+
// updates the status, filters the own event, and skips the write entirely if nothing changed
31+
context.resourceOperations().serverSideApplyPrimaryStatus(webPage);
32+
return UpdateControl.noUpdate();
33+
}
34+
```
35+
36+
By default these operations **match the desired state against the actual (cached) state before
37+
writing**: if they already match, the write is skipped; otherwise the write is performed and its own
38+
event is filtered. This is the most efficient option — it covers full event filtering *and* avoids a
39+
request to the Kubernetes API server when nothing changed. Default matchers are provided for every
40+
operation type; when one does not fit, supply your own via `Options.matchAndFilter(matcher)`.
41+
42+
`Options` exposes the available strategies:
43+
44+
- `matchAndFilter(...)` / `matchAndFilterWithDefaultMatcher(...)` — match, then write-and-filter only
45+
if needed (the default).
46+
- `filterIfOptimisticLocking()` — filter the own event only when the write uses optimistic locking,
47+
otherwise just cache the response.
48+
- `cacheOnly()` — only cache the response (read-cache-after-write consistency), no filtering.
49+
- `forceFilterEvents()` — always filter (mostly internal usage).
50+
51+
> **Correctness note**: filtering an own event is only safe if the framework can tell an own write
52+
> apart from a concurrent third-party write. This requires **either** a matcher **or** optimistic
53+
> locking; otherwise a concurrent external update inside the filter window may be missed until the
54+
> next resync.
55+
56+
### More correct own-event filtering (no-op update edge case)
57+
58+
The read-cache-after-write own-event filter has been reworked to fix an edge case where a legitimate
59+
external change could be filtered out together with a controller's own **no-op** update — for
60+
example, when the spec was changed externally while the controller patched its status and that status
61+
patch turned out to be a no-op. The new matcher-based operations avoid issuing such no-op writes in
62+
the first place, and the filtering itself is now correct in these concurrent scenarios.
63+
1264
## Additional Improvements
1365

66+
- **`GenericKubernetesResourceMatcher.matchStatus(...)`**: a status-only counterpart to `match(...)`
67+
that compares just the `/status` subtree.
68+
- **New `Matcher` SPI**: `io.javaoperatorsdk.operator.api.reconciler.matcher.Matcher` lets you plug a
69+
custom matching strategy into `Options.matchAndFilter(...)`.
70+
- Extensive integration tests covering every `ResourceOperations` update/patch operation (primary,
71+
status, and secondary resources) against a real cluster.
72+
1473
## Migration Notes
1574

75+
There are **no breaking API changes**; existing code compiles and runs unchanged. However:
76+
77+
### `UpdateControl` no longer filters own events by default
78+
79+
Returning an `UpdateControl` (or `ErrorStatusUpdateControl`) still updates the resource and keeps the
80+
cache read-after-write consistent, but it **no longer filters the resulting own event by default**
81+
so the write may cause an additional (idempotent) reconciliation. This change fixes correctness edge
82+
cases where an event that should have propagated was previously swallowed.
83+
84+
If you relied on the previous filtering, perform the update through `ResourceOperations` and return
85+
`UpdateControl.noUpdate()`:
86+
87+
```java
88+
// before (v5.4)
89+
resource.setStatus(new MyStatus().setReady(true));
90+
return UpdateControl.patchStatus(resource);
91+
92+
// after (v5.5) — filter the own event explicitly
93+
resource.setStatus(new MyStatus().setReady(true));
94+
context.resourceOperations().serverSideApplyPrimaryStatus(resource);
95+
return UpdateControl.noUpdate();
96+
```
97+
98+
See the [migration guide](/docs/migration/v5-5-migration) for details.
99+
16100
## Getting Started
17101

18102
```xml

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/resourceoperations/ResourceOperationsReconciler.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.concurrent.atomic.AtomicInteger;
1919

20+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
2021
import io.javaoperatorsdk.operator.api.reconciler.Context;
2122
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
2223
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
@@ -65,13 +66,20 @@ public UpdateControl<ResourceOperationsCustomResource> reconcile(
6566
case SSA -> {
6667
markResource(resource);
6768
// SSA does not use optimistic locking
69+
resource.getMetadata().setManagedFields(null);
6870
resource.getMetadata().setResourceVersion(null);
6971
ops.serverSideApplyPrimary(resource);
7072
}
7173
case SSA_STATUS -> {
72-
resource.setStatus(new ResourceOperationsStatus().setValue(STATUS_VALUE));
73-
resource.getMetadata().setResourceVersion(null);
74-
ops.serverSideApplyPrimaryStatus(resource);
74+
ResourceOperationsCustomResource fresh = new ResourceOperationsCustomResource();
75+
fresh.setMetadata(new ObjectMetaBuilder()
76+
.withName(resource.getMetadata().getName())
77+
.withNamespace(resource.getMetadata().getNamespace())
78+
.build());
79+
80+
fresh.setStatus(new ResourceOperationsStatus().setValue(STATUS_VALUE));
81+
fresh.getMetadata().setResourceVersion(null);
82+
ops.serverSideApplyPrimaryStatus(fresh);
7583
}
7684
case UPDATE -> {
7785
markResource(resource);

0 commit comments

Comments
 (0)