package org.infinispan.distribution.rehash;
import static org.infinispan.test.concurrent.StateSequencerUtil.advanceOnComponentMethod;
import static org.infinispan.test.concurrent.StateSequencerUtil.advanceOnGlobalComponentMethod;
import static org.infinispan.test.concurrent.StateSequencerUtil.matchMethodCall;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import java.util.Collections;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.statetransfer.StateConsumer;
import org.infinispan.test.MultipleCacheManagersTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.concurrent.InvocationMatcher;
import org.infinispan.test.concurrent.StateSequencer;
import org.infinispan.topology.ClusterTopologyManager;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.impl.LocalTransaction;
import org.infinispan.transaction.impl.RemoteTransaction;
import org.infinispan.transaction.impl.TransactionTable;
import org.infinispan.util.ControlledConsistentHashFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
/**
* Tests that state transfer properly replicates locks in a pessimistic cache, when the
* originator of the transaction is/was the primary owner.
*
* See ISPN-4091, ISPN-4108
*
* @author Dan Berindei
* @since 7.0
*/
@Test(groups = "functional", testName = "distribution.rehash.PessimisticStateTransferLocksTest")
public class PessimisticStateTransferLocksTest extends MultipleCacheManagersTest {
private static final String KEY = "key";
private static final String VALUE = "value";
{
cleanup = CleanupPhase.AFTER_METHOD;
}
private StateSequencer sequencer;
private ControlledConsistentHashFactory consistentHashFactory;
@AfterMethod(alwaysRun = true)
public void printSequencerState() {
log.debugf("Sequencer state: %s", sequencer);
sequencer.stop();
}
@Override
protected void createCacheManagers() throws Throwable {
ConfigurationBuilder c = getConfigurationBuilder();
addClusterEnabledCacheManager(c);
addClusterEnabledCacheManager(c);
addClusterEnabledCacheManager(c);
waitForClusterToForm();
}
protected ConfigurationBuilder getConfigurationBuilder() {
consistentHashFactory = new ControlledConsistentHashFactory(0, 1);
ConfigurationBuilder c = new ConfigurationBuilder();
c.clustering().cacheMode(CacheMode.DIST_SYNC);
c.clustering().hash().consistentHashFactory(consistentHashFactory).numSegments(1);
c.transaction().transactionMode(TransactionMode.TRANSACTIONAL);
c.transaction().lockingMode(LockingMode.PESSIMISTIC);
return c;
}
public void testPutStartedBeforeRebalance() throws Exception {
sequencer = new StateSequencer();
sequencer.logicalThread("tx", "tx:perform_op", "tx:check_locks", "tx:before_commit", "tx:after_commit");
sequencer.logicalThread("rebalance", "rebalance:before_get_tx", "rebalance:after_get_tx",
"rebalance:before_confirm", "rebalance:end");
sequencer.order("tx:perform_op", "rebalance:before_get_tx", "rebalance:after_get_tx", "tx:check_locks",
"rebalance:before_confirm", "rebalance:end", "tx:before_commit");
startTxWithPut();
startRebalance();
checkLocksBeforeCommit(false);
waitRebalanceEnd();
endTx();
checkLocksAfterCommit();
}
public void testLockStartedBeforeRebalance() throws Exception {
sequencer = new StateSequencer();
sequencer.logicalThread("tx", "tx:perform_op", "tx:check_locks", "tx:before_commit", "tx:after_commit");
sequencer.logicalThread("rebalance", "rebalance:before_get_tx", "rebalance:after_get_tx",
"rebalance:before_confirm", "rebalance:end");
sequencer.order("tx:perform_op", "rebalance:before_get_tx", "rebalance:after_get_tx", "tx:check_locks",
"rebalance:before_confirm", "rebalance:end", "tx:before_commit");
startTxWithLock();
startRebalance();
checkLocksBeforeCommit(false);
waitRebalanceEnd();
endTx();
checkLocksAfterCommit();
}
public void testPutStartedDuringRebalance() throws Exception {
sequencer = new StateSequencer();
sequencer.logicalThread("tx", "tx:perform_op", "tx:check_locks", "tx:before_commit",
"tx:after_commit");
sequencer.logicalThread("rebalance", "rebalance:before_get_tx", "rebalance:after_get_tx",
"rebalance:before_confirm", "rebalance:end");
sequencer.order("rebalance:after_get_tx", "tx:perform_op", "tx:check_locks",
"rebalance:before_confirm", "rebalance:end", "tx:before_commit");
startRebalance();
startTxWithPut();
checkLocksBeforeCommit(true);
waitRebalanceEnd();
endTx();
checkLocksAfterCommit();
}
public void testLockStartedDuringRebalance() throws Exception {
sequencer = new StateSequencer();
sequencer.logicalThread("tx", "tx:perform_op", "tx:check_locks", "tx:before_commit", "tx:after_commit");
sequencer.logicalThread("rebalance", "rebalance:before_get_tx", "rebalance:after_get_tx",
"rebalance:before_confirm", "rebalance:end");
sequencer.order("rebalance:after_get_tx", "tx:perform_op", "tx:check_locks",
"rebalance:before_confirm", "rebalance:end", "tx:before_commit");
startRebalance();
startTxWithLock();
checkLocksBeforeCommit(true);
waitRebalanceEnd();
endTx();
checkLocksAfterCommit();
}
private void startTxWithPut() throws Exception {
sequencer.enter("tx:perform_op");
tm(0).begin();
cache(0).put(KEY, VALUE);
sequencer.exit("tx:perform_op");
}
private void startTxWithLock() throws Exception {
sequencer.enter("tx:perform_op");
tm(0).begin();
advancedCache(0).lock(KEY);
sequencer.exit("tx:perform_op");
}
private void startRebalance() throws Exception {
InvocationMatcher rebalanceCompletedMatcher = matchMethodCall("handleRebalancePhaseConfirm")
.withParam(1, address(2)).matchCount(0).build();
advanceOnGlobalComponentMethod(sequencer, manager(0), ClusterTopologyManager.class,
rebalanceCompletedMatcher).before("rebalance:before_confirm");
InvocationMatcher localRebalanceMatcher = matchMethodCall("onTopologyUpdate").withParam(1, true).matchCount(0).build();
advanceOnComponentMethod(sequencer, cache(2), StateConsumer.class,
localRebalanceMatcher).before("rebalance:before_get_tx").after("rebalance:after_get_tx");
consistentHashFactory.setOwnerIndexes(2, 1);
consistentHashFactory.triggerRebalance(cache(0));
}
private void waitRebalanceEnd() throws Exception {
sequencer.advance("rebalance:end");
TestingUtil.waitForNoRebalance(caches());
}
private void endTx() throws Exception {
sequencer.advance("tx:before_commit");
tm(0).commit();
}
private void checkLocksBeforeCommit(boolean backupLockOnCache1) throws Exception {
sequencer.enter("tx:check_locks");
assertFalse(getTransactionTable(cache(0)).getLocalTransactions().isEmpty());
assertTrue(getTransactionTable(cache(0)).getRemoteTransactions().isEmpty());
LocalTransaction localTx = getTransactionTable(cache(0)).getLocalTransactions().iterator().next();
assertEquals(Collections.singleton(KEY), localTx.getLockedKeys());
assertEquals(Collections.emptySet(), localTx.getBackupLockedKeys());
assertTrue(getTransactionTable(cache(1)).getLocalTransactions().isEmpty());
assertEquals(backupLockOnCache1, !getTransactionTable(cache(1)).getRemoteTransactions().isEmpty());
assertTrue(getTransactionTable(cache(2)).getLocalTransactions().isEmpty());
assertFalse(getTransactionTable(cache(2)).getRemoteTransactions().isEmpty());
RemoteTransaction remoteTx = getTransactionTable(cache(2)).getRemoteTransactions().iterator().next();
assertEquals(Collections.emptySet(), remoteTx.getLockedKeys());
assertEquals(Collections.singleton(KEY), remoteTx.getBackupLockedKeys());
sequencer.exit("tx:check_locks");
}
private void checkLocksAfterCommit() {
for (Cache<Object, Object> c : caches()) {
final TransactionTable txTable = getTransactionTable(c);
assertTrue(txTable.getLocalTransactions().isEmpty());
eventually(new Condition() {
@Override
public boolean isSatisfied() throws Exception {
return txTable.getRemoteTransactions().isEmpty();
}
});
}
}
private TransactionTable getTransactionTable(Cache<Object, Object> c) {
return TestingUtil.extractComponent(c, TransactionTable.class);
}
}