package org.infinispan.notifications.cachelistener.cluster;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.concurrent.BrokenBarrierException;
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.distribution.TriangleDistributionInterceptor;
import org.infinispan.remoting.transport.Address;
import org.infinispan.test.TestingUtil;
import org.testng.annotations.Test;
/**
* Cluster listener test having a configuration of non tx and dist
*
* @author wburns
* @since 7.0
*/
@Test(groups = "functional", testName = "notifications.cachelistener.cluster.ClusterListenerDistTest")
public class ClusterListenerDistTest extends AbstractClusterListenerNonTxTest {
public ClusterListenerDistTest() {
super(false, CacheMode.DIST_SYNC);
}
@Test
public void testPrimaryOwnerGoesDownBeforeSendingEvent() 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(2);
BlockingInterceptor blockingInterceptor = new BlockingInterceptor<>(barrier, PutKeyValueCommand.class, true, false);
cache1.getAdvancedCache().getAsyncInterceptorChain().addInterceptorBefore(blockingInterceptor, TriangleDistributionInterceptor.class);
final MagicKey key = new MagicKey(cache1, cache2);
Future<String> future = fork(() -> cache0.put(key, FIRST_VALUE));
// Wait until the primary owner has sent the put command successfully to backup
barrier.await(10, TimeUnit.SECONDS);
awaitForBackups(cache0);
// Kill the cache now - note this will automatically unblock the fork thread
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(future.get(10, TimeUnit.SECONDS), FIRST_VALUE);
TestingUtil.waitForNoRebalance(cache0, cache2);
// The command is retried during rebalance, but there are two topologies - in the first (rebalancing) topology
// one node can be primary owner and in the second (rebalanced) the other. In this case, it's possible that
// the listener is fired both in the first topology and then after the response from primary owner arrives
// and the originator now has become the new primary owner.
// Similar situation is possible with triangle algorithm (TODO pruivo: elaborate)
assertTrue(clusterListener.events.size() >= 1);
assertTrue(clusterListener.events.size() <= 2);
Address cache0primary = cache0.getAdvancedCache().getDistributionManager().getCacheTopology().getDistribution(key).primary();
Address cache2primary = cache2.getAdvancedCache().getDistributionManager().getCacheTopology().getDistribution(key).primary();
// we expect that now both nodes have the same topology
assertEquals(cache0primary, cache2primary);
checkEvent(clusterListener.events.get(0), key, false, true);
// This is possible after rebalance; when rebalancing, primary owner is always the old backup
if (clusterListener.events.size() == 2) {
checkEvent(clusterListener.events.get(1), key, false, true);
}
}
}