package org.infinispan.statetransfer; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import java.util.concurrent.atomic.AtomicInteger; import javax.transaction.Status; import org.infinispan.Cache; import org.infinispan.commands.tx.CommitCommand; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.commands.tx.RollbackCommand; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.container.DataContainer; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.distribution.MagicKey; import org.infinispan.interceptors.AsyncInterceptorChain; import org.infinispan.interceptors.base.CommandInterceptor; import org.infinispan.interceptors.impl.CallInterceptor; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.TestingUtil; import org.infinispan.transaction.impl.TransactionTable; import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup; import org.infinispan.transaction.tm.EmbeddedTransaction; import org.infinispan.transaction.tm.EmbeddedTransactionManager; import org.testng.annotations.Test; /** * Tests the prepare replay. * * @author Pedro Ruivo * @since 6.0 */ @Test(groups = "functional", testName = "statetransfer.TxReplayTest") public class TxReplayTest extends MultipleCacheManagersTest { private static final String VALUE = "value"; public void testReplay() throws Exception { assertClusterSize("Wrong cluster size", 3); final Object key = new MagicKey(cache(0), cache(1)); final Cache<Object, Object> newBackupOwnerCache = cache(2); final TxCommandInterceptor interceptor = TxCommandInterceptor.inject(newBackupOwnerCache); EmbeddedTransactionManager transactionManager = (EmbeddedTransactionManager) tm(0); transactionManager.begin(); cache(0).put(key, VALUE); final EmbeddedTransaction transaction = transactionManager.getTransaction(); transaction.runPrepare(); assertEquals("Wrong transaction status before killing backup owner.", Status.STATUS_PREPARED, transaction.getStatus()); //now, we kill cache(1). the transaction is prepared in cache(1) and it should be forward to cache(2) killMember(1); checkIfTransactionExists(newBackupOwnerCache); assertEquals("Wrong transaction status after killing backup owner.", Status.STATUS_PREPARED, transaction.getStatus()); transaction.runCommit(false); assertNoTransactions(); assertEquals("Wrong number of prepares!", 1, interceptor.numberPrepares.get()); assertEquals("Wrong number of commits!", 1, interceptor.numberCommits.get()); assertEquals("Wrong number of rollbacks!", 0, interceptor.numberRollbacks.get()); checkKeyInDataContainer(key); } @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder builder = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, true); builder.transaction() .useSynchronization(false) .transactionManagerLookup(new EmbeddedTransactionManagerLookup()) .recovery().disable(); builder.clustering() .hash().numOwners(2) .stateTransfer().fetchInMemoryState(true); createClusteredCaches(3, builder); } private void checkKeyInDataContainer(Object key) { for (Cache<Object, Object> cache : caches()) { DataContainer container = cache.getAdvancedCache().getDataContainer(); InternalCacheEntry entry = container.get(key); assertNotNull("Cache '" + address(cache) + "' does not contain key!", entry); assertEquals("Cache '" + address(cache) + "' has wrong value!", VALUE, entry.getValue()); } } private void checkIfTransactionExists(Cache<Object, Object> cache) { TransactionTable table = TestingUtil.extractComponent(cache, TransactionTable.class); assertFalse("Expected a remote transaction.", table.getRemoteTransactions().isEmpty()); } private static class TxCommandInterceptor extends CommandInterceptor { //counters private final AtomicInteger numberPrepares = new AtomicInteger(0); private final AtomicInteger numberCommits = new AtomicInteger(0); private final AtomicInteger numberRollbacks = new AtomicInteger(0); @Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (!ctx.isOriginLocal()) { numberPrepares.incrementAndGet(); } return invokeNextInterceptor(ctx, command); } @Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { if (!ctx.isOriginLocal()) { numberCommits.incrementAndGet(); } return invokeNextInterceptor(ctx, command); } @Override public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable { if (!ctx.isOriginLocal()) { numberRollbacks.incrementAndGet(); } return invokeNextInterceptor(ctx, command); } public static TxCommandInterceptor inject(Cache cache) { AsyncInterceptorChain chain = cache.getAdvancedCache().getAsyncInterceptorChain(); if (chain.containsInterceptorType(TxCommandInterceptor.class)) { return chain.findInterceptorWithClass(TxCommandInterceptor.class); } TxCommandInterceptor interceptor = new TxCommandInterceptor(); chain.addInterceptorBefore(interceptor, CallInterceptor.class); return interceptor; } } }