Skip to content

Commit c461a84

Browse files
committed
fix: Fix NPE in ReconciliationDispatcher when custom resource cannot be retrieved on finalizer removal retry
1 parent 3a63ab3 commit c461a84

File tree

2 files changed

+31
-6
lines changed

2 files changed

+31
-6
lines changed

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -294,12 +294,23 @@ private PostExecutionControl<P> handleCleanup(P resource,
294294
// cleanup is finished, nothing left to done
295295
final var finalizerName = configuration().getFinalizerName();
296296
if (deleteControl.isRemoveFinalizer() && resource.hasFinalizer(finalizerName)) {
297-
P customResource = conflictRetryingUpdate(resource, r -> r.removeFinalizer(finalizerName));
297+
P customResource = conflictRetryingUpdate(resource, r -> {
298+
// the operator might not be allowed to retrieve the resource on a retry, e.g. when its
299+
// permissions are removed by deleting the namespace concurrently
300+
if (r == null) {
301+
log.warn(
302+
"Could not remove finalizer on null resource: {} with version: {}",
303+
getUID(resource),
304+
getVersion(resource));
305+
return false;
306+
}
307+
return r.removeFinalizer(finalizerName);
308+
});
298309
return PostExecutionControl.customResourceFinalizerRemoved(customResource);
299310
}
300311
}
301312
log.debug(
302-
"Skipping finalizer remove for resource: {} with version: {}. delete control: {}, uses finalizer: {} ",
313+
"Skipping finalizer remove for resource: {} with version: {}. delete control: {}, uses finalizer: {}",
303314
getUID(resource),
304315
getVersion(resource),
305316
deleteControl,

Diff for: operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java

+18-4
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,24 @@ void retriesFinalizerRemovalWithFreshResource() {
240240
verify(customResourceFacade, times(1)).getResource(any(), any());
241241
}
242242

243+
@Test
244+
void nullResourceIsGracefullyHandledOnFinalizerRemovalRetry() {
245+
// simulate the operator not able or not be allowed to get the custom resource during the retry
246+
// of the finalizer removal
247+
testCustomResource.addFinalizer(DEFAULT_FINALIZER);
248+
markForDeletion(testCustomResource);
249+
when(customResourceFacade.updateResource(any()))
250+
.thenThrow(new KubernetesClientException(null, 409, null));
251+
when(customResourceFacade.getResource(any(), any())).thenReturn(null);
252+
253+
var postExecControl =
254+
reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
255+
256+
assertThat(postExecControl.isFinalizerRemoved()).isTrue();
257+
verify(customResourceFacade, times(1)).updateResource(testCustomResource);
258+
verify(customResourceFacade, times(1)).getResource(any(), any());
259+
}
260+
243261
@Test
244262
void throwsExceptionIfFinalizerRemovalRetryExceeded() {
245263
testCustomResource.addFinalizer(DEFAULT_FINALIZER);
@@ -307,8 +325,6 @@ void doesNotAddFinalizerIfConfiguredNotTo() {
307325
assertEquals(0, testCustomResource.getMetadata().getFinalizers().size());
308326
}
309327

310-
311-
312328
@Test
313329
void doesNotRemovesTheSetFinalizerIfTheDeleteNotMethodInstructsIt() {
314330
testCustomResource.addFinalizer(DEFAULT_FINALIZER);
@@ -590,7 +606,6 @@ void errorStatusHandlerCanPatchResource() {
590606
reconciler.errorHandler =
591607
(r, ri, e) -> ErrorStatusUpdateControl.patchStatus(testCustomResource);
592608

593-
594609
reconciliationDispatcher.handleExecution(
595610
new ExecutionScope(null).setResource(testCustomResource));
596611

@@ -673,7 +688,6 @@ TestCustomResource createResourceWithFinalizer() {
673688
return resourceWithFinalizer;
674689
}
675690

676-
677691
private void removeFinalizers(CustomResource customResource) {
678692
customResource.getMetadata().getFinalizers().clear();
679693
}

0 commit comments

Comments
 (0)