Skip to content

Commit 43bbdf4

Browse files
csviriCopilot
andauthored
fix: assorted robustness and clarity fixes (#3533)
* fix: assorted robustness and clarity fixes Three unrelated small items found while auditing. ManagedInformerEventSource.get could throw instead of falling back to the informer cache. It compared a temporary-cache hit against `manager().lastSyncResourceVersion(namespace)` without guarding either failure mode: * `lastSyncResourceVersion` is null until an informer has completed its initial list, and `compareResourceVersions` dereferences it, so this threw a NullPointerException. * `InformerManager.lastSyncResourceVersion` does `getSource(ns) .orElseThrow()`, which throws `NoSuchElementException` for a namespace that is no longer watched after a dynamic namespace change - a window that only closes on the next re-list, when `checkGhostResources` prunes the entry. `TemporaryResourceCache.putResource` already guards the null case; the comparison is now extracted into a helper that guards both and falls back to the informer cache, which is the safe answer in either case. ExpectationResult.name() threw a bare NullPointerException when no expectation was registered, which is a normal result state produced by `ExpectationManager.checkExpectation`. It now throws `IllegalStateException` pointing at `isExpectationPresent()`, and says so in the javadoc. ResourceOperations cleanups: drops the `options` parameter of `desiredForJsonPatch`, which was never read at any of its six call sites, and rewrites the comment on `create` - it claimed the operation "check if the resource already exists", which it does not; the actual reason filtering is safe is that the API server rejects a duplicate create. * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix 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>
1 parent b8a477d commit 43bbdf4

3 files changed

Lines changed: 46 additions & 13 deletions

File tree

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,9 @@ public <R extends HasMetadata> R update(
406406
* @return the created resource as returned by the API server
407407
*/
408408
public <R extends HasMetadata> R create(R resource) {
409-
// it is safe to do event filtering for create since check if the resource already exists.
409+
// filtering the own event is safe here without optimistic locking or a matcher: a create either
410+
// succeeds, in which case the event we filter is unambiguously ours, or the API server rejects
411+
// it because the resource already exists.
410412
return create(resource, Options.forceFilterEvents());
411413
}
412414

@@ -445,7 +447,7 @@ public <R extends HasMetadata> R create(
445447
if (informerEventSource == null) {
446448
return create(resource);
447449
}
448-
// it is safe to do event filtering for create since check if the resource already exists.
450+
// see the note on create(R) about why filtering the own event is safe here
449451
return resourcePatch(
450452
resource, r -> context.getClient().resource(r).create(), informerEventSource, options);
451453
}
@@ -556,7 +558,7 @@ public <R extends HasMetadata> R jsonPatch(R actualResource, UnaryOperator<R> un
556558
*/
557559
public <R extends HasMetadata> R jsonPatch(
558560
R actualResource, UnaryOperator<R> unaryOperator, Options options) {
559-
R desired = desiredForJsonPatch(actualResource, unaryOperator, options);
561+
R desired = desiredForJsonPatch(actualResource, unaryOperator);
560562
return resourcePatch(
561563
desired,
562564
actualResource,
@@ -580,7 +582,7 @@ public <R extends HasMetadata> R jsonPatch(
580582
UnaryOperator<R> unaryOperator,
581583
InformerEventSource<R, P> informerEventSource,
582584
Options options) {
583-
R desired = desiredForJsonPatch(actualResource, unaryOperator, options);
585+
R desired = desiredForJsonPatch(actualResource, unaryOperator);
584586
return resourcePatch(
585587
desired,
586588
actualResource,
@@ -620,7 +622,7 @@ public <R extends HasMetadata> R jsonPatchStatus(
620622
*/
621623
public <R extends HasMetadata> R jsonPatchStatus(
622624
R actualResource, UnaryOperator<R> unaryOperator, Options options) {
623-
R desired = desiredForJsonPatch(actualResource, unaryOperator, options);
625+
R desired = desiredForJsonPatch(actualResource, unaryOperator);
624626
return resourcePatch(
625627
desired,
626628
actualResource,
@@ -645,7 +647,7 @@ public <R extends HasMetadata> R jsonPatchStatus(
645647
UnaryOperator<R> unaryOperator,
646648
InformerEventSource<R, P> informerEventSource,
647649
Options options) {
648-
R desired = desiredForJsonPatch(actualResource, unaryOperator, options);
650+
R desired = desiredForJsonPatch(actualResource, unaryOperator);
649651
return resourcePatch(
650652
desired,
651653
actualResource,
@@ -680,7 +682,7 @@ public P jsonPatchPrimary(P actualResource, UnaryOperator<P> unaryOperator) {
680682
* @return the patched resource as returned by the API server
681683
*/
682684
public P jsonPatchPrimary(P actualResource, UnaryOperator<P> unaryOperator, Options options) {
683-
P desired = desiredForJsonPatch(actualResource, unaryOperator, options);
685+
P desired = desiredForJsonPatch(actualResource, unaryOperator);
684686
return resourcePatch(
685687
desired,
686688
actualResource,
@@ -717,7 +719,7 @@ public P jsonPatchPrimaryStatus(P actualResource, UnaryOperator<P> unaryOperator
717719
*/
718720
public P jsonPatchPrimaryStatus(
719721
P actualResource, UnaryOperator<P> unaryOperator, Options options) {
720-
P desired = desiredForJsonPatch(actualResource, unaryOperator, options);
722+
P desired = desiredForJsonPatch(actualResource, unaryOperator);
721723
return resourcePatch(
722724
desired,
723725
actualResource,
@@ -1447,7 +1449,7 @@ public enum Mode {
14471449
}
14481450

14491451
private <T extends HasMetadata> T desiredForJsonPatch(
1450-
T actualResource, UnaryOperator<T> unaryOperator, Options options) {
1452+
T actualResource, UnaryOperator<T> unaryOperator) {
14511453
var cloned =
14521454
context
14531455
.getControllerConfiguration()

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,7 @@ public Optional<R> get(ResourceID resourceID) {
224224
Optional<R> resource = temporaryResourceCache.getResourceFromCache(resourceID);
225225
if (comparableResourceVersions
226226
&& resource.isPresent()
227-
&& ReconcilerUtilsInternal.compareResourceVersions(
228-
resource.get().getMetadata().getResourceVersion(),
229-
manager().lastSyncResourceVersion(resource.get().getMetadata().getNamespace()))
230-
> 0) {
227+
&& isLaterThanLastSyncResourceVersion(resource.orElseThrow())) {
231228
log.debug("Latest resource found in temporary cache for Resource ID: {}", resourceID);
232229
return resource;
233230
} else {
@@ -242,6 +239,31 @@ public Optional<R> get(ResourceID resourceID) {
242239
}
243240
}
244241

242+
/**
243+
* A resource from the temporary cache is only preferred over the informer cache if we can tell
244+
* that it is newer. The last sync resource version is not available before an informer has
245+
* completed its initial list, and the namespace might not be watched anymore after a dynamic
246+
* namespace change, in both of which cases the informer cache is used instead.
247+
*/
248+
private boolean isLaterThanLastSyncResourceVersion(R resource) {
249+
var namespace = resource.getMetadata().getNamespace();
250+
if (!manager().isWatchingNamespace(namespace)) {
251+
return false;
252+
}
253+
final String lastSyncResourceVersion;
254+
try {
255+
lastSyncResourceVersion = manager().lastSyncResourceVersion(namespace);
256+
} catch (java.util.NoSuchElementException e) {
257+
return false;
258+
}
259+
if (lastSyncResourceVersion == null) {
260+
return false;
261+
}
262+
return ReconcilerUtilsInternal.compareResourceVersions(
263+
resource.getMetadata().getResourceVersion(), lastSyncResourceVersion)
264+
> 0;
265+
}
266+
245267
/**
246268
* @deprecated Use {@link #get(ResourceID)} instead.
247269
*/

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/ExpectationResult.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@ public boolean isNotPresentOrFulfilled() {
3636
return !isExpectationPresent() || isFulfilled();
3737
}
3838

39+
/**
40+
* @return the name of the expectation this result refers to
41+
* @throws IllegalStateException if there is no expectation, check {@link #isExpectationPresent()}
42+
* first
43+
*/
3944
public String name() {
45+
if (expectation == null) {
46+
throw new IllegalStateException(
47+
"No expectation present for this result, check isExpectationPresent() first.");
48+
}
4049
return expectation.name();
4150
}
4251
}

0 commit comments

Comments
 (0)