package org.infinispan.statetransfer; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.distribution.ch.ConsistentHash; import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachelistener.annotation.DataRehashed; import org.infinispan.notifications.cachelistener.event.DataRehashedEvent; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.CleanupAfterMethod; import org.testng.annotations.Test; /** * @author Dan Berindei * @since 5.2 */ @Test(groups = "functional", testName = "statetransfer.DataRehashedEventTest") @CleanupAfterMethod public class DataRehashedEventTest extends MultipleCacheManagersTest { private ConfigurationBuilder defaultConfig = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, false); private DataRehashedListener rehashListener; @Override protected void createCacheManagers() throws Throwable { createClusteredCaches(1, defaultConfig); } public void testJoinAndLeave() { Cache<Object, Object> c1 = cache(0); rehashListener = new DataRehashedListener(); c1.addListener(rehashListener); ConsistentHash ch1Node = advancedCache(0).getDistributionManager().getReadConsistentHash(); assertEquals(rehashListener.removeEvents().size(), 0); // start a second node and wait for the rebalance to end addClusterEnabledCacheManager(defaultConfig); cache(1); TestingUtil.waitForNoRebalance(cache(0), cache(1)); ConsistentHash ch2Nodes = advancedCache(0).getDistributionManager().getReadConsistentHash(); rehashListener.waitForEvents(2); List<DataRehashedEvent<Object, Object>> events = rehashListener.removeEvents(); assertEquals(events.size(), 2); DataRehashedEvent<Object, Object> pre = events.get(0); DataRehashedEvent<Object, Object> post = events.get(1); assertTrue(pre.isPre()); assertEquals(pre.getConsistentHashAtStart(), ch1Node); // we could get this "intermediate" CH with TopologyChanged events, but this should be enough assertNotNull(pre.getConsistentHashAtEnd()); assertEquals(pre.getMembersAtEnd(), ch2Nodes.getMembers()); assertFalse(post.isPre()); assertEquals(post.getConsistentHashAtStart(), ch1Node); assertEquals(post.getConsistentHashAtEnd(), ch2Nodes); // start a third node and wait for the rebalance to end addClusterEnabledCacheManager(defaultConfig); cache(2); TestingUtil.waitForNoRebalance(cache(0), cache(1), cache(2)); ConsistentHash ch3Nodes = advancedCache(0).getDistributionManager().getReadConsistentHash(); rehashListener.waitForEvents(2); events = rehashListener.removeEvents(); assertEquals(events.size(), 2); pre = events.get(0); post = events.get(1); assertTrue(pre.isPre()); assertEquals(pre.getConsistentHashAtStart(), ch2Nodes); // we could get this "intermediate" CH with TopologyChanged events, but this should be enough assertNotNull(pre.getConsistentHashAtEnd()); assertEquals(pre.getMembersAtEnd(), ch3Nodes.getMembers()); assertFalse(post.isPre()); assertEquals(post.getConsistentHashAtStart(), ch2Nodes); assertEquals(post.getConsistentHashAtEnd(), ch3Nodes); // stop cache 2 and wait for the rebalance to end killMember(2); // this CH might be different than the CH before the 3rd node joined ConsistentHash chAfterLeave = advancedCache(0).getDistributionManager().getReadConsistentHash(); rehashListener.waitForEvents(2); events = rehashListener.removeEvents(); assertEquals(events.size(), 2); pre = events.get(0); post = events.get(1); assertTrue(pre.isPre()); // we could get this "intermediate" CH with TopologyChanged events, but this should be enough assertNotNull(pre.getConsistentHashAtStart()); assertEquals(pre.getMembersAtStart(), chAfterLeave.getMembers()); assertEquals(pre.getConsistentHashAtEnd(), chAfterLeave); assertFalse(post.isPre()); assertEquals(post.getConsistentHashAtStart(), pre.getConsistentHashAtStart()); assertEquals(post.getConsistentHashAtEnd(), pre.getConsistentHashAtEnd()); // stop cache 1 and wait for the rebalance to end killMember(1); // cache 0 was already an owner for all the segments, so there shouldn't be any rebalance events = rehashListener.removeEvents(); assertEquals(events.size(), 0); } public void testPostOnlyEvent() { Cache<Object, Object> c1 = cache(0); rehashListener = new DataRehashedListenerPostOnly(); c1.addListener(rehashListener); ConsistentHash ch1Node = advancedCache(0).getDistributionManager().getReadConsistentHash(); assertEquals(rehashListener.removeEvents().size(), 0); // start a second node and wait for the rebalance to end addClusterEnabledCacheManager(defaultConfig); cache(1); TestingUtil.waitForNoRebalance(cache(0), cache(1)); rehashListener.waitForEvents(1); } @Listener public class DataRehashedListener { private volatile List<DataRehashedEvent<Object, Object>> events = new CopyOnWriteArrayList<DataRehashedEvent<Object, Object>>(); @DataRehashed public void onDataRehashed(DataRehashedEvent<Object, Object> e) { log.tracef("New event received: %s", e); events.add(e); } List<DataRehashedEvent<Object, Object>> removeEvents() { List<DataRehashedEvent<Object, Object>> oldEvents = events; events = new CopyOnWriteArrayList<DataRehashedEvent<Object, Object>>(); return oldEvents; } void waitForEvents(final int count) { eventually(new Condition() { @Override public boolean isSatisfied() throws Exception { return events.size() >= count; } }); } } @Listener(observation = Listener.Observation.POST) public class DataRehashedListenerPostOnly extends DataRehashedListener { } }