|
25 | 25 | import io.fabric8.kubernetes.client.KubernetesClient; |
26 | 26 | import io.fabric8.kubernetes.client.KubernetesClientException; |
27 | 27 | import io.fabric8.kubernetes.client.dsl.MixedOperation; |
| 28 | +import io.fabric8.kubernetes.client.dsl.NamespaceableResource; |
28 | 29 | import io.fabric8.kubernetes.client.dsl.Resource; |
| 30 | +import io.javaoperatorsdk.operator.MockKubernetesClient; |
29 | 31 | import io.javaoperatorsdk.operator.TestUtils; |
| 32 | +import io.javaoperatorsdk.operator.api.config.BaseConfigurationService; |
30 | 33 | import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; |
| 34 | +import io.javaoperatorsdk.operator.api.config.ResolvedControllerConfiguration; |
| 35 | +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; |
| 36 | +import io.javaoperatorsdk.operator.processing.Controller; |
31 | 37 | import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever; |
32 | 38 | import io.javaoperatorsdk.operator.processing.event.source.EventSource; |
33 | 39 | import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource; |
@@ -247,6 +253,39 @@ void retriesFinalizerRemovalWithFreshResource() { |
247 | 253 | verify(resourceOp, times(1)).get(); |
248 | 254 | } |
249 | 255 |
|
| 256 | + @Test |
| 257 | + void removeFinalizerHandlesAlreadyDeletedTargetGracefully() { |
| 258 | + // When the underlying patch returns null because the target resource is already gone |
| 259 | + // on the API server (fabric8's edit() returns null on 404), removeFinalizer must |
| 260 | + // return cleanly rather than throw |
| 261 | + var resource = TestUtils.testCustomResource1(); |
| 262 | + resource.getMetadata().setResourceVersion("1"); |
| 263 | + resource.addFinalizer(FINALIZER_NAME); |
| 264 | + when(context.getPrimaryResource()).thenReturn(resource); |
| 265 | + |
| 266 | + // Real ControllerEventSource so eventFilteringUpdateAndCacheResource and the |
| 267 | + // TemporaryResourceCache it talks to are wired up the way they would be in production |
| 268 | + NamespaceableResource<TestCustomResource> single = mock(); |
| 269 | + when(single.edit(any(UnaryOperator.class))).thenReturn(null); |
| 270 | + var client = MockKubernetesClient.client(TestCustomResource.class); |
| 271 | + when(client.resource(any(TestCustomResource.class))).thenReturn(single); |
| 272 | + |
| 273 | + var eventSource = new ControllerEventSource<>(testController(client)); |
| 274 | + eventSource.start(); |
| 275 | + try { |
| 276 | + when(context.getClient()).thenReturn(client); |
| 277 | + var eventSourceRetriever = mock(EventSourceRetriever.class); |
| 278 | + when(eventSourceRetriever.getControllerEventSource()).thenReturn(eventSource); |
| 279 | + when(context.eventSourceRetriever()).thenReturn(eventSourceRetriever); |
| 280 | + |
| 281 | + var result = resourceOperations.removeFinalizer(FINALIZER_NAME); |
| 282 | + |
| 283 | + assertThat(result).isNull(); |
| 284 | + } finally { |
| 285 | + eventSource.stop(); |
| 286 | + } |
| 287 | + } |
| 288 | + |
250 | 289 | @Test |
251 | 290 | void resourcePatchWithSingleEventSource() { |
252 | 291 | var resource = TestUtils.testCustomResource1(); |
@@ -324,4 +363,28 @@ void resourcePatchThrowsWhenEventSourceIsNotManagedInformer() { |
324 | 363 | assertThat(exception.getMessage()).contains("Target event source must be a subclass off"); |
325 | 364 | assertThat(exception.getMessage()).contains("ManagedInformerEventSource"); |
326 | 365 | } |
| 366 | + |
| 367 | + private Controller<TestCustomResource> testController(KubernetesClient client) { |
| 368 | + var informerConfig = |
| 369 | + InformerConfiguration.builder(TestCustomResource.class) |
| 370 | + .withComparableResourceVersions(true) |
| 371 | + .withGhostResourceCacheCheckInterval(Constants.DEFAULT_GHOST_RESOURCE_CHECK_INTERVAL) |
| 372 | + .buildForController(); |
| 373 | + var configuration = |
| 374 | + new ResolvedControllerConfiguration<>( |
| 375 | + "test", |
| 376 | + true, |
| 377 | + null, |
| 378 | + null, |
| 379 | + null, |
| 380 | + null, |
| 381 | + FINALIZER_NAME, |
| 382 | + null, |
| 383 | + null, |
| 384 | + new BaseConfigurationService(), |
| 385 | + informerConfig, |
| 386 | + false, |
| 387 | + null); |
| 388 | + return new Controller<>((r, ctx) -> UpdateControl.noUpdate(), configuration, client); |
| 389 | + } |
327 | 390 | } |
0 commit comments