package org.infinispan.tx.totalorder.simple; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.cache.Configurations; import org.infinispan.interceptors.AsyncInterceptorChain; import org.infinispan.interceptors.locking.OptimisticLockingInterceptor; import org.infinispan.interceptors.locking.PessimisticLockingInterceptor; import org.infinispan.interceptors.totalorder.TotalOrderDistributionInterceptor; import org.infinispan.interceptors.totalorder.TotalOrderInterceptor; import org.infinispan.interceptors.totalorder.TotalOrderVersionedDistributionInterceptor; import org.infinispan.interceptors.totalorder.TotalOrderVersionedEntryWrappingInterceptor; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.transaction.TransactionProtocol; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.util.TransactionTrackInterceptor; import org.infinispan.util.concurrent.IsolationLevel; import org.testng.Assert; import org.testng.annotations.Test; /** * @author Pedro Ruivo * @since 5.3 */ @Test(groups = "functional") public abstract class BaseSimpleTotalOrderTest extends MultipleCacheManagersTest { private static final String KEY_1 = "key_1"; private static final String KEY_2 = "key_2"; private static final String KEY_3 = "key_3"; private static final String VALUE_1 = "value_1"; private static final String VALUE_2 = "value_2"; private static final String VALUE_3 = "value_3"; private static final String VALUE_4 = "value_4"; private static final int TX_TIMEOUT = 15; //seconds private final int clusterSize; private final CacheMode mode; private final boolean writeSkew; private final boolean useSynchronization; private final TransactionTrackInterceptor[] transactionTrackInterceptors; private final int index1; private final int index2; protected BaseSimpleTotalOrderTest(int clusterSize, CacheMode mode, boolean writeSkew, boolean useSynchronization) { this.clusterSize = clusterSize; this.mode = mode; this.writeSkew = writeSkew; this.useSynchronization = useSynchronization; this.transactionTrackInterceptors = new TransactionTrackInterceptor[clusterSize]; this.index1 = 0; this.index2 = clusterSize > 1 ? 1 : 0; this.cleanup = CleanupPhase.AFTER_METHOD; } public final void testInterceptorChain() { AsyncInterceptorChain ic = advancedCache(0).getAsyncInterceptorChain(); assertTrue(ic.containsInterceptorType(TotalOrderInterceptor.class)); if (writeSkew) { assertFalse(ic.containsInterceptorType(TotalOrderDistributionInterceptor.class)); assertTrue(ic.containsInterceptorType(TotalOrderVersionedDistributionInterceptor.class)); assertTrue(ic.containsInterceptorType(TotalOrderVersionedEntryWrappingInterceptor.class)); } else { assertTrue(ic.containsInterceptorType(TotalOrderDistributionInterceptor.class)); assertFalse(ic.containsInterceptorType(TotalOrderVersionedDistributionInterceptor.class)); assertFalse(ic.containsInterceptorType(TotalOrderVersionedEntryWrappingInterceptor.class)); } assertFalse(ic.containsInterceptorType(OptimisticLockingInterceptor.class)); assertFalse(ic.containsInterceptorType(PessimisticLockingInterceptor.class)); } public final void testToCacheIsTransactional() { assertTrue(cache(0).getCacheConfiguration().transaction().transactionMode().isTransactional()); } public void testSinglePhaseTotalOrder() { assertTrue(Configurations.isOnePhaseTotalOrderCommit(cache(0).getCacheConfiguration())); } public final void testPut() throws InterruptedException { preCheckBeforeTest(KEY_1, KEY_2, KEY_3); assertNull(cache(index1).put(KEY_1, VALUE_1)); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); assertEquals(cache(index2).put(KEY_1, VALUE_2), VALUE_1); assertTransactionSeenByEverybody(index2, true, KEY_1); assertCacheValue(KEY_1, VALUE_2); Map<Object, Object> map = new HashMap<>(); map.put(KEY_2, VALUE_2); map.put(KEY_3, VALUE_3); cache(index1).putAll(map); assertTransactionSeenByEverybody(index1, true, KEY_2, KEY_3); assertCacheValue(KEY_2, VALUE_2); assertCacheValue(KEY_3, VALUE_3); map = new HashMap<>(); map.put(KEY_2, VALUE_3); map.put(KEY_3, VALUE_2); cache(index2).putAll(map); assertTransactionSeenByEverybody(index2, true, KEY_2, KEY_3); assertCacheValue(KEY_2, VALUE_3); assertCacheValue(KEY_3, VALUE_2); assertNoTransactions(); } public void removeTest() throws InterruptedException { preCheckBeforeTest(KEY_1); cache(index1).put(KEY_1, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); assertEquals(cache(index1).remove(KEY_1), VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, null); cache(index1).put(KEY_1, VALUE_2); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_2); assertEquals(cache(index2).remove(KEY_1), VALUE_2); assertTransactionSeenByEverybody(index2, true, KEY_1); assertCacheValue(KEY_1, null); assertNoTransactions(); } public void testPutIfAbsent() throws InterruptedException { preCheckBeforeTest(KEY_1, KEY_2, KEY_3); cache(index1).put(KEY_1, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); assertEquals(cache(index1).putIfAbsent(KEY_1, VALUE_2), VALUE_1); assertTransactionSeenByEverybody(index1, false); //the putIfAbsent is not successful and it will not be replicated assertCacheValue(KEY_1, VALUE_1); assertEquals(cache(index2).putIfAbsent(KEY_1, VALUE_3), VALUE_1); assertTransactionSeenByEverybody(index2, false); //the putIfAbsent is not successful and it will not be replicated assertCacheValue(KEY_1, VALUE_1); assertNull(cache(index1).putIfAbsent(KEY_2, VALUE_1)); assertTransactionSeenByEverybody(index1, true, KEY_2); assertCacheValue(KEY_2, VALUE_1); assertNull(cache(index2).putIfAbsent(KEY_3, VALUE_1)); assertTransactionSeenByEverybody(index2, true, KEY_3); assertCacheValue(KEY_3, VALUE_1); assertNoTransactions(); } public void testRemoveIfPresent() throws InterruptedException { preCheckBeforeTest(KEY_1); cache(index1).put(KEY_1, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); assertFalse(cache(index1).remove(KEY_1, VALUE_2)); assertTransactionSeenByEverybody(index1, false); //the removeIfPresent is not successful and it will not be replicated assertCacheValue(KEY_1, VALUE_1); assertTrue(cache(index1).remove(KEY_1, VALUE_1)); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, null); cache(index1).put(KEY_1, VALUE_3); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_3); assertFalse(cache(index2).remove(KEY_1, VALUE_2)); assertTransactionSeenByEverybody(index2, false); //the removeIfPresent is not successful and it will not be replicated assertCacheValue(KEY_1, VALUE_3); assertTrue(cache(index2).remove(KEY_1, VALUE_3)); assertTransactionSeenByEverybody(index2, true, KEY_1); assertCacheValue(KEY_1, null); assertNoTransactions(); } public void testClear() throws InterruptedException { preCheckBeforeTest(KEY_1); cache(index1).put(KEY_1, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); cache(index1).clear(); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, null); cache(index1).put(KEY_1, VALUE_2); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_2); cache(index2).clear(); assertTransactionSeenByEverybody(index2, true, KEY_1); assertCacheValue(KEY_1, null); assertNoTransactions(); } public void testReplace() throws InterruptedException { preCheckBeforeTest(KEY_1); cache(index1).put(KEY_1, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); assertEquals(cache(index1).replace(KEY_1, VALUE_2), VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_2); assertEquals(cache(index2).replace(KEY_1, VALUE_3), VALUE_2); assertTransactionSeenByEverybody(index2, true, KEY_1); assertCacheValue(KEY_1, VALUE_3); assertNoTransactions(); } public void testReplaceIfPresent() throws InterruptedException { preCheckBeforeTest(KEY_1, KEY_2); cache(index1).put(KEY_1, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); assertFalse(cache(index1).replace(KEY_1, VALUE_2, VALUE_3)); assertTransactionSeenByEverybody(index1, false); //the replaceIfPresent is not successful and it will not be replicated assertCacheValue(KEY_1, VALUE_1); assertTrue(cache(index1).replace(KEY_1, VALUE_1, VALUE_4)); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_4); cache(index1).put(KEY_2, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_2); assertCacheValue(KEY_2, VALUE_1); assertFalse(cache(index1).replace(KEY_2, VALUE_2, VALUE_3)); assertTransactionSeenByEverybody(index1, false); //the replaceIfPresent is not successful and it will not be replicated assertCacheValue(KEY_2, VALUE_1); assertTrue(cache(index1).replace(KEY_2, VALUE_1, VALUE_4)); assertTransactionSeenByEverybody(index1, true, KEY_2); assertCacheValue(KEY_2, VALUE_4); assertNoTransactions(); } public void testReplaceWithOldVal() throws InterruptedException { preCheckBeforeTest(KEY_1); cache(index1).put(KEY_1, VALUE_1); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); cache(index1).put(KEY_1, VALUE_2); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_2); assertEquals(cache(index1).replace(KEY_1, VALUE_1), VALUE_2); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); cache(index1).put(KEY_1, VALUE_3); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, VALUE_3); assertEquals(cache(index2).replace(KEY_1, VALUE_1), VALUE_3); assertTransactionSeenByEverybody(index2, true, KEY_1); assertCacheValue(KEY_1, VALUE_1); assertNoTransactions(); } public void testRemoveUnexistingEntry() throws InterruptedException { preCheckBeforeTest(KEY_1); assertNull(cache(index1).remove(KEY_1)); assertTransactionSeenByEverybody(index1, true, KEY_1); assertCacheValue(KEY_1, null); assertNull(cache(index2).remove(KEY_1)); assertTransactionSeenByEverybody(index2, true, KEY_1); assertCacheValue(KEY_1, null); assertNoTransactions(); } @Override protected final void createCacheManagers() throws Throwable { ConfigurationBuilder dcc = getDefaultClusteredCacheConfig(mode, true); dcc.transaction().transactionProtocol(TransactionProtocol.TOTAL_ORDER); dcc.locking().isolationLevel(writeSkew ? IsolationLevel.REPEATABLE_READ : IsolationLevel.READ_COMMITTED); dcc.transaction().useSynchronization(useSynchronization); dcc.clustering().hash().numOwners(2); dcc.transaction().recovery().disable(); createCluster(dcc, clusterSize); waitForClusterToForm(); for (int i = 0; i < clusterSize; ++i) { transactionTrackInterceptors[i] = TransactionTrackInterceptor.injectInCache(cache(i)); transactionTrackInterceptors[i].reset(); } } protected void preCheckBeforeTest(Object... keys) { for (Cache cache : caches()) { for (Object key : keys) { assertNull(cache.get(key)); } } for (TransactionTrackInterceptor interceptor : transactionTrackInterceptors) { interceptor.reset(); } } //originatorIndex == cache which executed the transaction protected void assertCacheValue(Object key, Object value) { for (Cache cache : caches()) { assertEquals(cache.get(key), value, "Wrong value for cache " + address(cache) + ". key=" + key); } } protected abstract boolean isOwner(Cache cache, Object key); private void assertTransactionSeenByEverybody(int index, boolean checkAllInvolvedNodes, Object... keys) throws InterruptedException { //index == cache index that executed the transaction. GlobalTransaction lastExecutedTx = transactionTrackInterceptors[index].getLastExecutedTransaction(); assertEquals(transactionTrackInterceptors[index].getExecutedTransactions().size(), 1); if (!checkAllInvolvedNodes) { Assert.assertTrue(transactionTrackInterceptors[index].awaitForLocalCompletion(lastExecutedTx, TX_TIMEOUT, TimeUnit.SECONDS), "Transaction didn't complete locally in " + address(cache(index)) + "."); for (TransactionTrackInterceptor interceptor : transactionTrackInterceptors) { interceptor.reset(); } } else { for (int i = 0; i < clusterSize; ++i) { if (i == index) { //the cache that executed the transaction needs to wait for the local termination assertTrue(transactionTrackInterceptors[i].awaitForLocalCompletion(lastExecutedTx, TX_TIMEOUT, TimeUnit.SECONDS), "Transaction didn't complete locally in " + address(cache(i)) + "."); } for (Object key : keys) { if (i == index || isOwner(cache(i), key)) { //we only need to check for caches that owns the keys //or the cache that executed the transaction because the transaction is self deliver. assertTrue(transactionTrackInterceptors[i].awaitForRemoteCompletion(lastExecutedTx, TX_TIMEOUT, TimeUnit.SECONDS), "Transaction didn't arrive to " + address(cache(i)) + ". Key is " + key); break; } } transactionTrackInterceptors[i].reset(); } } } }