Skip to content

Commit f850a83

Browse files
committed
ghost clear concurrency tests
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent 0685672 commit f850a83

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ private String getLastSyncResourceVersion(String namespace) {
231231
return managedInformerEventSource.manager().lastSyncResourceVersion(namespace);
232232
}
233233

234-
// todo tests with combination of event processing
235234
/**
236235
* There are (probably extremely rare) circumstances, when we can miss a delete event related to a
237236
* resources: when we create a resource that is deleted right after by third party and the related

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Optional;
2020
import java.util.Set;
2121
import java.util.concurrent.CountDownLatch;
22+
import java.util.concurrent.Executors;
2223
import java.util.concurrent.ScheduledExecutorService;
2324

2425
import org.junit.jupiter.api.BeforeEach;
@@ -339,6 +340,116 @@ void multipleCachingFilteringUpdates_variant4() {
339340
assertNoEventProduced();
340341
}
341342

343+
@Test
344+
void ghostCheckRemovesCachedResourceDuringFilteringUpdate() {
345+
var mes = mock(ManagedInformerEventSource.class);
346+
var mim = mock(InformerManager.class);
347+
when(mes.manager()).thenReturn(mim);
348+
when(mim.isWatchingNamespace(any())).thenReturn(true);
349+
when(mim.lastSyncResourceVersion(any())).thenReturn("1");
350+
when(mim.get(any())).thenReturn(Optional.empty());
351+
352+
var ghostCheckExecutor = Executors.newScheduledThreadPool(1);
353+
temporaryResourceCache = spy(new TemporaryResourceCache<>(true, 50, ghostCheckExecutor, mes));
354+
informerEventSource.setTemporalResourceCache(temporaryResourceCache);
355+
356+
// put resource in cache and start a filtering update
357+
var deployment = deploymentWithResourceVersion(2);
358+
temporaryResourceCache.putResource(deployment);
359+
var resourceId = ResourceID.fromResource(deployment);
360+
temporaryResourceCache.startEventFilteringModify(resourceId);
361+
362+
// advance sync version so ghost check considers the cached resource outdated
363+
when(mim.lastSyncResourceVersion(any())).thenReturn("3");
364+
365+
// ghost check should remove the cached resource
366+
await()
367+
.untilAsserted(
368+
() -> assertThat(temporaryResourceCache.getResourceFromCache(resourceId)).isEmpty());
369+
370+
// complete the filtering update - the resource should not reappear
371+
temporaryResourceCache.doneEventFilterModify(resourceId, "2");
372+
assertThat(temporaryResourceCache.getResourceFromCache(resourceId)).isEmpty();
373+
374+
ghostCheckExecutor.shutdownNow();
375+
}
376+
377+
@Test
378+
void ghostCheckRunsConcurrentlyWithPutResource() {
379+
var mes = mock(ManagedInformerEventSource.class);
380+
var mim = mock(InformerManager.class);
381+
when(mes.manager()).thenReturn(mim);
382+
when(mim.isWatchingNamespace(any())).thenReturn(true);
383+
when(mim.lastSyncResourceVersion(any())).thenReturn("1");
384+
when(mim.get(any())).thenReturn(Optional.empty());
385+
386+
var ghostCheckExecutor = Executors.newScheduledThreadPool(1);
387+
temporaryResourceCache = spy(new TemporaryResourceCache<>(true, 50, ghostCheckExecutor, mes));
388+
informerEventSource.setTemporalResourceCache(temporaryResourceCache);
389+
390+
// put a resource that will become a ghost
391+
var deployment = deploymentWithResourceVersion(2);
392+
temporaryResourceCache.putResource(deployment);
393+
394+
// advance sync version so ghost check removes it
395+
when(mim.lastSyncResourceVersion(any())).thenReturn("3");
396+
397+
await()
398+
.untilAsserted(
399+
() ->
400+
assertThat(
401+
temporaryResourceCache.getResourceFromCache(
402+
ResourceID.fromResource(deployment)))
403+
.isEmpty());
404+
405+
// now put a newer resource - should succeed even after ghost removal
406+
var newerDeployment = deploymentWithResourceVersion(4);
407+
temporaryResourceCache.putResource(newerDeployment);
408+
assertThat(
409+
temporaryResourceCache.getResourceFromCache(ResourceID.fromResource(newerDeployment)))
410+
.isPresent();
411+
412+
ghostCheckExecutor.shutdownNow();
413+
}
414+
415+
@Test
416+
void filteringUpdateAndGhostCheckWithNamespaceChange() {
417+
var mes = mock(ManagedInformerEventSource.class);
418+
var mim = mock(InformerManager.class);
419+
when(mes.manager()).thenReturn(mim);
420+
when(mim.isWatchingNamespace(any())).thenReturn(true);
421+
when(mim.lastSyncResourceVersion(any())).thenReturn("1");
422+
when(mim.get(any())).thenReturn(Optional.empty());
423+
424+
var ghostCheckExecutor = Executors.newScheduledThreadPool(1);
425+
temporaryResourceCache = spy(new TemporaryResourceCache<>(true, 50, ghostCheckExecutor, mes));
426+
informerEventSource.setTemporalResourceCache(temporaryResourceCache);
427+
428+
// start filtering update and put resource
429+
var deployment = deploymentWithResourceVersion(2);
430+
var resourceId = ResourceID.fromResource(deployment);
431+
temporaryResourceCache.startEventFilteringModify(resourceId);
432+
temporaryResourceCache.putResource(deployment);
433+
434+
// namespace becomes unwatched - ghost check should clean up
435+
when(mim.isWatchingNamespace(any())).thenReturn(false);
436+
437+
await()
438+
.untilAsserted(
439+
() -> assertThat(temporaryResourceCache.getResourceFromCache(resourceId)).isEmpty());
440+
441+
// complete the filtering update
442+
var doneResult = temporaryResourceCache.doneEventFilterModify(resourceId, "2");
443+
// resource was already cleaned by ghost check, so no deferred event
444+
assertThat(doneResult).isEmpty();
445+
446+
// put should be rejected since namespace is no longer watched
447+
temporaryResourceCache.putResource(deploymentWithResourceVersion(3));
448+
assertThat(temporaryResourceCache.getResourceFromCache(resourceId)).isEmpty();
449+
450+
ghostCheckExecutor.shutdownNow();
451+
}
452+
342453
private void assertNoEventProduced() {
343454
await()
344455
.pollDelay(Duration.ofMillis(50))

0 commit comments

Comments
 (0)