package org.infinispan.notifications.cachelistener.cluster;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.infinispan.Cache;
import org.infinispan.commands.write.PutKeyValueCommand;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.distribution.BlockingInterceptor;
import org.infinispan.distribution.MagicKey;
import org.infinispan.interceptors.impl.EntryWrappingInterceptor;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.test.TestingUtil;
import org.infinispan.tx.dld.ControlledRpcManager;
import org.testng.annotations.Test;
/**
* Cluster listener test having a configuration of non tx and replication
*
* @author wburns
* @since 7.0
*/
@Test(groups = "functional", testName = "notifications.cachelistener.cluster.ClusterListenerReplTest")
public class ClusterListenerReplTest extends AbstractClusterListenerNonTxTest {
public ClusterListenerReplTest() {
super(false, CacheMode.REPL_SYNC);
}
public void testPrimaryOwnerGoesDownBeforeBackupRaisesEvent() throws InterruptedException, TimeoutException,
ExecutionException, BrokenBarrierException {
final Cache<Object, String> cache0 = cache(0, CACHE_NAME);
Cache<Object, String> cache1 = cache(1, CACHE_NAME);
Cache<Object, String> cache2 = cache(2, CACHE_NAME);
ClusterListener clusterListener = new ClusterListener();
cache0.addListener(clusterListener);
// Now we want to block the outgoing put to the backup owner
RpcManager rpcManager = TestingUtil.extractComponent(cache1, RpcManager.class);
ControlledRpcManager controlledRpcManager = new ControlledRpcManager(rpcManager);
controlledRpcManager.blockBefore(PutKeyValueCommand.class);
TestingUtil.replaceComponent(cache1, RpcManager.class, controlledRpcManager, true);
final MagicKey key = new MagicKey(cache1, cache2);
Future<String> future = fork(new Callable<String>() {
@Override
public String call() throws Exception {
return cache0.put(key, FIRST_VALUE);
}
});
// Wait until the primary owner has sent the put command successfully to backup
controlledRpcManager.waitForCommandToBlock(10, TimeUnit.SECONDS);
// Kill the cache now
TestingUtil.killCacheManagers(cache1.getCacheManager());
// This should return null normally, but since it was retried it returns it's own value :(
// Maybe some day this can work properly
assertEquals(null, future.get(10, TimeUnit.SECONDS));
// We should have received an event that was marked as retried - maybe even two!
assertTrue(clusterListener.events.size() >= 1);
assertTrue(clusterListener.events.size() <= 2);
checkEvent(clusterListener.events.get(0), key, true, true);
}
public void testPrimaryOwnerGoesDownAfterBackupRaisesEvent() throws InterruptedException, TimeoutException,
ExecutionException, BrokenBarrierException {
final Cache<Object, String> cache0 = cache(0, CACHE_NAME);
Cache<Object, String> cache1 = cache(1, CACHE_NAME);
Cache<Object, String> cache2 = cache(2, CACHE_NAME);
ClusterListener clusterListener = new ClusterListener();
cache0.addListener(clusterListener);
CyclicBarrier barrier = new CyclicBarrier(3);
BlockingInterceptor blockingInterceptor0 = new BlockingInterceptor<>(barrier, PutKeyValueCommand.class,
true, false);
cache0.getAdvancedCache().getAsyncInterceptorChain().addInterceptorBefore(blockingInterceptor0, EntryWrappingInterceptor.class);
BlockingInterceptor blockingInterceptor2 = new BlockingInterceptor<>(barrier, PutKeyValueCommand.class,
true, false);
cache2.getAdvancedCache().getAsyncInterceptorChain().addInterceptorBefore(blockingInterceptor2, EntryWrappingInterceptor.class);
// this is a replicated cache, all other nodes are backup owners
final MagicKey key = new MagicKey(cache1);
Future<String> future = fork(() -> cache0.put(key, FIRST_VALUE));
// Wait until the primary owner has sent the put command successfully to both backups
barrier.await(10, TimeUnit.SECONDS);
// Remove the interceptor so the next command can proceed properly
cache0.getAdvancedCache().getAsyncInterceptorChain().removeInterceptor(BlockingInterceptor.class);
cache2.getAdvancedCache().getAsyncInterceptorChain().removeInterceptor(BlockingInterceptor.class);
blockingInterceptor0.suspend(true);
blockingInterceptor2.suspend(true);
// Kill the cache now - note this will automatically unblock the fork thread
TestingUtil.killCacheManagers(cache1.getCacheManager());
// Unblock the command
barrier.await(10, TimeUnit.SECONDS);
// This should return null normally, but since it was retried it returns it's own value :(
// Maybe some day this can work properly
String returnValue = future.get(10, TimeUnit.SECONDS);
assertEquals(FIRST_VALUE, returnValue);
assertTrue(clusterListener.events.size() >= 2);
assertTrue(clusterListener.events.size() <= 3);
// First create should not be retried since it was sent before node failure.
checkEvent(clusterListener.events.get(0), key, true, false);
// We should receive a retry event since it doesn't know the exact state, but it will be a MODIFY since
// CREATE was already done
checkEvent(clusterListener.events.get(1), key, false, true);
}
}