package io.eguan.dtx; /* * #%L * Project eguan * %% * Copyright (C) 2012 - 2017 Oodrive * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import static io.eguan.dtx.DtxDummyRmFactory.DEFAULT_PAYLOAD; import static io.eguan.dtx.DtxDummyRmFactory.newResMgrThatDoesEverythingRight; import static io.eguan.dtx.DtxNodeState.INITIALIZED; import static io.eguan.dtx.DtxNodeState.NOT_INITIALIZED; import static io.eguan.dtx.DtxNodeState.STARTED; import static io.eguan.dtx.DtxResourceManagerState.UP_TO_DATE; import static io.eguan.dtx.DtxTaskStatus.COMMITTED; import static io.eguan.dtx.DtxTaskStatus.ROLLED_BACK; import static io.eguan.dtx.DtxTaskStatus.UNKNOWN; import static io.eguan.dtx.DtxTestHelper.DEFAULT_HZ_PORT; import static io.eguan.dtx.DtxTestHelper.awaitStateUpdate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import io.eguan.dtx.DtxConstants; import io.eguan.dtx.DtxManager; import io.eguan.dtx.DtxManagerConfig; import io.eguan.dtx.DtxNode; import io.eguan.dtx.DtxNodeState; import io.eguan.dtx.DtxRequestQueueAdm; import io.eguan.dtx.DtxResourceManager; import io.eguan.dtx.DtxResourceManagerAdm; import io.eguan.dtx.DtxTaskAdm; import io.eguan.dtx.DtxTaskStatus; import io.eguan.dtx.TransactionManager; import io.eguan.dtx.DtxDummyRmFactory.DefaultPrepareAnswer; import io.eguan.dtx.DtxDummyRmFactory.DtxResourceManagerBuilder; import io.eguan.dtx.DtxEventListeners.ErrorGeneratingEventListener; import io.eguan.dtx.DtxEventListeners.HazelcastNodeCountListener; import io.eguan.dtx.DtxEventListeners.ResMgrStateCountListener; import io.eguan.dtx.DtxResourceManagerAdm.DtxJournalStatus; import io.eguan.dtx.events.DtxClusterEvent; import io.eguan.dtx.events.DtxEvent; import io.eguan.dtx.journal.JournalRotationManager; import io.eguan.dtx.journal.WritableTxJournal; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.transaction.xa.XAException; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runners.model.InitializationError; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; /** * Tests for the complete lifecycle of the {@link DtxManager}. * * @author oodrive * @author pwehrle * @author ebredzinski * */ public final class TestDtxManager { private static final Logger LOGGER = LoggerFactory.getLogger(TestDtxManager.class); private static final int NB_OF_PEERS = 10; private static final int NB_OF_RES_MGR = 20; private static final int DEFAULT_PEER_PORT = 22222; private static final String DEFAULT_PEER_ADDR = "127.0.0.1"; private static final long DEFAULT_TX_TIMEOUT_MS = 10000L; private static final int TASK_WAIT_RETRY_COUNT = 10; private static final int TASK_WAIT_TIME_MS = 1000; private static final int TEST_NB_OF_TEST_ENTRIES = 30; private static final long TEST_ROTATION_THRESHOLD = 3221225472L; // keep this high to avoid rotation private static final int TASK_MIN_DURATION_MS = 500; private DtxManager targetDtxMgr; private Path tmpJournalDir; private DtxManagerConfig dtxConfig; /** * Set up instance fixture. * * This initializes the {@link #targetDtxMgr} field with a functional {@link DtxManager} instance. * * @throws InitializationError * if setting up fails * * @see #targetDtxMgr */ @Before public final void setUp() throws InitializationError { try { this.tmpJournalDir = Files.createTempDirectory(TestDtxManager.class.getSimpleName()); } catch (final IOException e) { throw new InitializationError(e); } dtxConfig = DtxTestHelper.newDtxManagerConfig(tmpJournalDir); targetDtxMgr = new DtxManager(dtxConfig); assertEquals(dtxConfig.getLocalPeer().getNodeId(), targetDtxMgr.getNodeId()); } /** * Tears down instance fixture. * * This shuts down the {@link #targetDtxMgr} {@link DtxManager} instance properly. * * @throws InitializationError * if tearing down fails * * @see #setUp() * @see #targetDtxMgr */ @After public final void tearDown() throws InitializationError { targetDtxMgr.stop(); assert (STARTED != targetDtxMgr.getStatus()); targetDtxMgr.fini(); try { io.eguan.utils.Files.deleteRecursive(tmpJournalDir); } catch (final IOException e) { throw new InitializationError(e); } } /** * Tests failed construction of a {@link DtxManager} due to a <code>null</code> argument. * * @throws NullPointerException * if the argument is <code>null</code>, expected for this test */ @Test(expected = NullPointerException.class) public final void testConstructionFailConfigNull() throws NullPointerException { LOGGER.info("Executing"); targetDtxMgr = new DtxManager(null); } /** * Tests the {@link DtxManager#init()} method. */ @Test public final void testInit() { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); // tests repeated init targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); } /** * Tests the {@link DtxManager#init()} method's failure due to a bad configuration (group name is <code>null</code> * ). * * @throws NullPointerException * expected for this test */ @Test(expected = NullPointerException.class) public final void testInitFailBadConfigGroupNull() throws NullPointerException { LOGGER.info("Executing"); final DtxManagerConfig defaultConfig = DtxTestHelper.newDtxManagerConfig(tmpJournalDir); final List<DtxNode> peerList = defaultConfig.getPeers(); final DtxManagerConfig badConfig = new DtxManagerConfig(DtxTestHelper.getDefaultConfiguration(), tmpJournalDir, null, "asecret", defaultConfig.getLocalPeer(), peerList.toArray(new DtxNode[peerList.size()])); targetDtxMgr = new DtxManager(badConfig); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); } /** * Tests the {@link DtxManager#init()} method's failure due to a bad configuration (secret is <code>null</code> ). * * @throws NullPointerException * expected for this test */ @Test(expected = NullPointerException.class) public final void testInitFailBadConfigSecretNull() { LOGGER.info("Executing"); final DtxManagerConfig defaultConfig = DtxTestHelper.newDtxManagerConfig(tmpJournalDir); final List<DtxNode> peerList = defaultConfig.getPeers(); final DtxManagerConfig badConfig = new DtxManagerConfig(DtxTestHelper.getDefaultConfiguration(), tmpJournalDir, "testCluster", null, defaultConfig.getLocalPeer(), peerList.toArray(new DtxNode[peerList.size()])); targetDtxMgr = new DtxManager(badConfig); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); } /** * Tests the {@link DtxManager#init()} method's failure due to a <code>null</code> * {@link io.eguan.configuration.MetaConfiguration}. * * @throws NullPointerException * expected for this test */ @Test(expected = NullPointerException.class) public final void testInitFailBadConfigMetaConfNull() { LOGGER.info("Executing"); final DtxManagerConfig defaultConfig = DtxTestHelper.newDtxManagerConfig(tmpJournalDir); final List<DtxNode> peerList = defaultConfig.getPeers(); final DtxManagerConfig badConfig = new DtxManagerConfig(null, tmpJournalDir, "testCluster", "asecret", defaultConfig.getLocalPeer(), peerList.toArray(new DtxNode[peerList.size()])); targetDtxMgr = new DtxManager(badConfig); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); } /** * Tests the {@link DtxManager#fini()} method. */ @Test public final void testFini() { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); targetDtxMgr.fini(); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); // test idempotence of fini targetDtxMgr.fini(); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); } /** * Tests the correct purge of peers upon stopping and restarting the {@link DtxManager}. */ @Test public final void testInitFiniResidue() { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final DtxNode newPeer = new DtxNode(UUID.randomUUID(), new InetSocketAddress(DEFAULT_PEER_ADDR, DEFAULT_PEER_PORT)); targetDtxMgr.registerPeer(newPeer); assertTrue(targetDtxMgr.getRegisteredPeers().contains(newPeer)); targetDtxMgr.fini(); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); assertFalse(targetDtxMgr.getRegisteredPeers().contains(newPeer)); } /** * Tests the {@link DtxManager#start()} method. */ @Test public final void testStart() { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); } /** * Tests the {@link DtxManager#start()} method's failure due to a bad configuration (local peer address is * <code>null</code>. */ @Test(expected = NullPointerException.class) public final void testStartFailBadConfigHostNull() { LOGGER.info("Executing"); final DtxManagerConfig defaultConfig = DtxTestHelper.newDtxManagerConfig(tmpJournalDir); final List<DtxNode> peerList = defaultConfig.getPeers(); final DtxManagerConfig badConfig = new DtxManagerConfig(DtxTestHelper.getDefaultConfiguration(), tmpJournalDir, "testCluster", "asecret", null, peerList.toArray(new DtxNode[peerList.size()])); targetDtxMgr = new DtxManager(badConfig); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); try { targetDtxMgr.start(); } catch (final IllegalArgumentException ie) { assertEquals(DtxNodeState.FAILED, targetDtxMgr.getStatus()); throw ie; } } /** * Tests the {@link DtxManager#stop()} method. */ @Test public final void testStop() { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); targetDtxMgr.stop(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); // test idempotence of stop targetDtxMgr.stop(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); } /** * Tests the triggering of lifecycle events on {@link DtxManager#start()} and {@link DtxManager#stop()}. * * @throws NullPointerException * if the registered listener is <code>null</code>, not part of this test * @throws IllegalStateException * if the {@link DtxManager} is not initialized, not part of this test * @throws InterruptedException * if waiting for synchronization is interrupted, not part of this test */ @Test public final void testRegisterDtxEventListenerAndStop() throws NullPointerException, IllegalStateException, InterruptedException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final CountDownLatch latch = new CountDownLatch(4); final Object shutdownListener = new HazelcastNodeCountListener(latch); targetDtxMgr.registerDtxEventListener(shutdownListener); // start doesn't trigger lifecycle events as construction of the hazelcast instance starts it targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); targetDtxMgr.stop(); assertTrue("Latch counted down before timeout", latch.await(DEFAULT_TX_TIMEOUT_MS, TimeUnit.MILLISECONDS)); targetDtxMgr.unregisterDtxEventListener(shutdownListener); } /** * Tests the {@link DtxManager#registerDtxEventListener(Object)} method's failure due to a <code>null</code> * argument. * * @throws NullPointerException * if the registered listener is <code>null</code>, expected for this test * @throws IllegalStateException * if the {@link DtxManager} is not initialized, not part of this test */ @Test(expected = NullPointerException.class) public final void testRegisterDtxEventListenerFailNull() throws NullPointerException, IllegalStateException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.registerDtxEventListener(null); } /** * Tests the {@link DtxManager#registerDtxEventListener(Object)} method's failure due to a <code>null</code>. * * @throws NullPointerException * if the registered listener is <code>null</code>, not part of this test * @throws IllegalStateException * if the {@link DtxManager} is not initialized, expected for this test */ @Test(expected = IllegalStateException.class) public final void testRegisterDtxEventListenerFailNotInitialized() throws NullPointerException, IllegalStateException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); final ErrorGeneratingEventListener badListener = new ErrorGeneratingEventListener(DtxEvent.class); try { targetDtxMgr.registerDtxEventListener(badListener); } catch (final IllegalStateException ie) { badListener.checkForAssertErrors(LOGGER); throw ie; } } /** * Tests the absence of triggered events on {@link DtxManager#stop()} after a listener is unregistered. * * @throws NullPointerException * if the registered listener is <code>null</code>, not part of this test * @throws IllegalStateException * if the {@link DtxManager} is not initialized, not part of this test */ @Test public final void testUnregisterDtxEventListener() throws NullPointerException, IllegalStateException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final ErrorGeneratingEventListener badClusterListener = new ErrorGeneratingEventListener(DtxClusterEvent.class); targetDtxMgr.registerDtxEventListener(badClusterListener); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); targetDtxMgr.unregisterDtxEventListener(badClusterListener); targetDtxMgr.stop(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); assertEquals(1, badClusterListener.getAssertErrors().size()); } /** * Tests the {@link DtxManager#unregisterDtxEventListener(Object)} method's failure due to a <code>null</code> * listener. * * @throws NullPointerException * if the registered listener is <code>null</code>, expected for this test * @throws IllegalStateException * if the {@link DtxManager} is not initialized, not part of this test */ @Test(expected = NullPointerException.class) public final void testUnregisterDtxEventListenerFailNull() throws NullPointerException, IllegalStateException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.registerDtxEventListener(new ErrorGeneratingEventListener()); targetDtxMgr.unregisterDtxEventListener(null); } /** * Tests the {@link DtxManager#unregisterDtxEventListener(Object)} method's failure due to an unregistered listener. * * @throws NullPointerException * if the registered listener is <code>null</code>, not part of this test * @throws IllegalStateException * if the {@link DtxManager} is not initialized, expected for this test */ @Test(expected = IllegalArgumentException.class) public final void testUnregisterDtxEventListenerFailNotRegd() throws NullPointerException, IllegalStateException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.registerDtxEventListener(new ErrorGeneratingEventListener()); targetDtxMgr.unregisterDtxEventListener(new ErrorGeneratingEventListener()); } /** * Tests the {@link DtxManager#registerPeer(DtxNode)}, {@link DtxManager#getRegisteredPeers()} and * {@link DtxManager#unregisterPeer(DtxNode)} methods. */ @Test public final void testRegisterUnregisterPeer() { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final ArrayList<DtxNode> registerPeers = new ArrayList<DtxNode>(); final int nbConfPeers = targetDtxMgr.getRegisteredPeers().size(); for (int i = nbConfPeers + 1; i <= nbConfPeers + NB_OF_PEERS; i++) { final UUID newUuid = UUID.randomUUID(); final InetSocketAddress newAddr = new InetSocketAddress(DEFAULT_PEER_ADDR, DEFAULT_PEER_PORT + i); final DtxNode newPeer = new DtxNode(newUuid, newAddr); targetDtxMgr.registerPeer(newPeer); final Set<DtxNode> registeredPeers = targetDtxMgr.getRegisteredPeers(); assertTrue(registeredPeers.contains(newPeer)); assertEquals(i, registeredPeers.size()); registerPeers.add(newPeer); // test idempotence of registerPeer targetDtxMgr.registerPeer(newPeer); assertTrue(targetDtxMgr.getRegisteredPeers().contains(newPeer)); } assertEquals(nbConfPeers + NB_OF_PEERS, targetDtxMgr.getRegisteredPeers().size()); for (final DtxNode currPeer : registerPeers) { targetDtxMgr.unregisterPeer(currPeer); assertFalse(targetDtxMgr.getRegisteredPeers().contains(currPeer)); // test idempotence of unregisterPeer targetDtxMgr.unregisterPeer(currPeer); assertFalse(targetDtxMgr.getRegisteredPeers().contains(currPeer)); } } /** * Tests the {@link DtxManager#registerPeer(DtxNode)} method's failure due to a non-initialized state. * * @throws IllegalStateException * expected for this test * @throws NullPointerException * if the argument is <code>null</code>, not part of this test */ @Test(expected = IllegalStateException.class) public final void testRegisterPeerFailNotInitialized() throws IllegalStateException, NullPointerException { LOGGER.info("Executing"); final DtxNode newPeer = new DtxNode(UUID.randomUUID(), new InetSocketAddress(DEFAULT_PEER_ADDR, DEFAULT_PEER_PORT)); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.registerPeer(newPeer); } /** * Tests the {@link DtxManager#registerPeer(DtxNode)} method's failure due to <code>null</code> argument. * * @throws IllegalStateException * not part of this test * * @throws NullPointerException * if the argument is <code>null</code>, expected for this test */ @Test(expected = NullPointerException.class) public final void testRegisterPeerFailPeerNull() throws IllegalStateException, NullPointerException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.registerPeer(null); } /** * Tests the {@link DtxManager#unregisterPeer(DtxNode)} method's failure due to a non-initialized state. * * @throws IllegalStateException * expected for this test * @throws NullPointerException * if the argument is <code>null</code>, not part of this test */ @Test(expected = IllegalStateException.class) public final void testUnregisterPeerFailNotInitialized() throws IllegalStateException, NullPointerException { LOGGER.info("Executing"); final DtxNode newPeer = new DtxNode(UUID.randomUUID(), new InetSocketAddress(DEFAULT_PEER_ADDR, DEFAULT_PEER_PORT)); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.unregisterPeer(newPeer); } /** * Tests the {@link DtxManager#unregisterPeer(DtxNode)} method's failure due to <code>null</code> argument. * * @throws IllegalStateException * not part of this test * * @throws NullPointerException * if the argument is <code>null</code>, expected for this test */ @Test(expected = NullPointerException.class) public final void testUnregisterPeerFailPeerNull() throws IllegalStateException, NullPointerException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.unregisterPeer(null); } /** * Tests the {@link DtxManager#getRegisteredPeers()} method's failure due to a non-initialized state. * * @throws IllegalStateException * expected for this test */ @Test(expected = IllegalStateException.class) public final void testGetRegisteredPeersFailNotInitialized() throws IllegalStateException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.getRegisteredPeers(); } /** * Tests the {@link DtxManager#registerResourceManager(DtxResourceManager)}, * {@link DtxManager#getRegisteredResourceManager(UUID)} and {@link DtxManager#unregisterResourceManager(UUID)} * methods. * * @throws XAException * if construction of mock {@link DtxResourceManager}s fails, not part of this test */ @Test public final void testRegisterUnregisterResourceManager() throws XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final ArrayList<DtxResourceManager> registerResMgrs = new ArrayList<>(); for (int i = 0; i < NB_OF_RES_MGR; i++) { final UUID resourceId = UUID.randomUUID(); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resourceId); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(resourceId)); registerResMgrs.add(newResMgr); } for (final DtxResourceManager currResMgr : registerResMgrs) { final UUID currId = currResMgr.getId(); targetDtxMgr.unregisterResourceManager(currId); assertNull(targetDtxMgr.getRegisteredResourceManager(currId)); } } /** * Tests the {@link DtxManager#registerResourceManager(DtxResourceManager)}, * {@link DtxManager#getRegisteredResourceManager(UUID)} and {@link DtxManager#unregisterResourceManager(UUID)} * methods on a {@link DtxManager#start() started} instance. * * @throws XAException * if construction of mock {@link DtxResourceManager}s fails, not part of this test * @throws InterruptedException * if interrupted while waiting for events to occur */ @Test public final void testRegisterUnregisterResourceManagerStarted() throws XAException, InterruptedException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); final CountDownLatch stateLatch = new CountDownLatch(2 * NB_OF_RES_MGR); final ResMgrStateCountListener stateListener = new ResMgrStateCountListener(stateLatch, null); targetDtxMgr.registerDtxEventListener(stateListener); final ArrayList<DtxResourceManager> registerResMgrs = new ArrayList<>(); for (int i = 0; i < NB_OF_RES_MGR; i++) { final UUID resourceId = UUID.randomUUID(); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resourceId); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(resourceId)); registerResMgrs.add(newResMgr); } for (final DtxResourceManager currResMgr : registerResMgrs) { final UUID currId = currResMgr.getId(); targetDtxMgr.unregisterResourceManager(currId); assertNull(targetDtxMgr.getRegisteredResourceManager(currId)); } assertTrue("Latch counted down before timeout", stateLatch.await(DEFAULT_TX_TIMEOUT_MS, TimeUnit.MILLISECONDS)); stateListener.checkForAssertErrors(LOGGER); } /** * Tests the {@link DtxManager#registerResourceManager(DtxResourceManager)} method's failure on duplicate * registration. * * @throws XAException * if construction of mock {@link DtxResourceManager}s fails, not part of this test * @throws IllegalArgumentException * expected for this test */ @Test(expected = IllegalArgumentException.class) public final void testRegisterResourceManagerFailDuplicates() throws XAException, IllegalArgumentException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final UUID resourceId = UUID.randomUUID(); final DtxResourceManager firstResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resourceId); assertNotNull(firstResMgr); targetDtxMgr.registerResourceManager(firstResMgr); assertEquals(firstResMgr, targetDtxMgr.getRegisteredResourceManager(resourceId)); // construct another instance final DtxResourceManager secondResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resourceId); assertNotNull(secondResMgr); assertFalse(firstResMgr.equals(secondResMgr)); targetDtxMgr.registerResourceManager(secondResMgr); } /** * Tests the {@link DtxManager#registerResourceManager(DtxResourceManager)} method's failure due to a * <code>null</code> argument. * * @throws NullPointerException * expected for this test */ @Test(expected = NullPointerException.class) public final void testRegisterResourceManagerFailNullArgument() throws NullPointerException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.registerResourceManager(null); } /** * Tests the {@link DtxManager#registerResourceManager(DtxResourceManager)} method's failure due to a * non-initialized state. * * @throws IllegalStateException * expected for this test * @throws NullPointerException * if the argument is <code>null</code>, not part of this test * @throws XAException * if construction of mock {@link DtxResourceManager}s fails, not part of this test */ @Test(expected = IllegalStateException.class) public final void testRegisterResourceManagerFailNotInitialized() throws IllegalStateException, NullPointerException, XAException { LOGGER.info("Executing"); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); } /** * Tests the {@link DtxManager#unregisterResourceManager(UUID)} method's failure due to a <code>null</code> * argument. * * @throws NullPointerException * expected for this test */ @Test(expected = NullPointerException.class) public final void testUnregisterResourceManagerFailNullArgument() throws NullPointerException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.unregisterResourceManager(null); } /** * Tests the {@link DtxManager#unregisterResourceManager(UUID)} method's failure due to a non-initialized state. * * @throws IllegalStateException * expected for this test * @throws NullPointerException * if the argument is <code>null</code>, not part of this test * @throws XAException * if construction of mock {@link DtxResourceManager}s fails, not part of this test */ @Test(expected = IllegalStateException.class) public final void testUnregisterResourceManagerFailNotInitialized() throws IllegalStateException, NullPointerException, XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(newResMgr.getId())); targetDtxMgr.fini(); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.unregisterResourceManager(newResMgr.getId()); } /** * Tests the {@link DtxManager#getRegisteredResourceManager(UUID)} method's failure due to a non-initialized state. * * @throws IllegalStateException * expected for this test * @throws NullPointerException * if the argument is <code>null</code>, not part of this test * @throws XAException * if construction of mock {@link DtxResourceManager}s fails, not part of this test */ @Test(expected = IllegalStateException.class) public final void testGetregisteredResourceManagerFailNotInitialized() throws IllegalStateException, NullPointerException, XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(newResMgr.getId())); targetDtxMgr.fini(); assertEquals(NOT_INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.getRegisteredResourceManager(newResMgr.getId()); } /** * Tests registering the same {@link DtxResourceManager} concurrently from multiple threads. * * @throws InterruptedException * if execution of the registering threads is interrupted, not part of this test * @throws XAException * if mock setup fails, not part of this test */ @Test public final void testConcurrentResourceManagerRegistrations() throws InterruptedException, XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null); assertNotNull(newResMgr); final ExecutorService executor = Executors.newCachedThreadPool(); final ArrayList<Callable<Void>> taskList = new ArrayList<Callable<Void>>(); // adds tasks concurrently trying to register the same resource manager for (int i = 0; i < NB_OF_PEERS; i++) { taskList.add(new Callable<Void>() { @Override public final Void call() throws Exception { targetDtxMgr.registerResourceManager(newResMgr); return null; } }); } // executes the tasks and counts the error cases. int errorCount = 0; for (final Future<Void> currResult : executor.invokeAll(taskList)) { try { currResult.get(); } catch (final ExecutionException e) { final Throwable cause = e.getCause(); assertNotNull(cause); assertEquals(IllegalArgumentException.class, cause.getClass()); errorCount++; } } // only one may have succeeded assertEquals(NB_OF_PEERS - 1, errorCount); } /** * Tests registering and unregistering the same {@link DtxResourceManager} concurrently from multiple threads. * * @throws InterruptedException * if execution of the registering or unregistering threads is interrupted, not part of this test * @throws XAException * if mock setup fails, not part of this test */ @Test public final void testConcurrentResourceManagerUnRegistrations() throws InterruptedException, XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final UUID resUuid = UUID.randomUUID(); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(newResMgr.getId())); final ExecutorService executor = Executors.newCachedThreadPool(); final ArrayList<Callable<Void>> taskList = new ArrayList<Callable<Void>>(); // adds tasks concurrently trying to register the same resource manager for (int i = 0; i < NB_OF_PEERS; i++) { taskList.add(new Callable<Void>() { @Override public final Void call() throws Exception { targetDtxMgr.registerResourceManager(newResMgr); return null; } }); } // adds tasks concurrently trying to unregister the same resource manager for (int i = 0; i < NB_OF_PEERS; i++) { taskList.add(new Callable<Void>() { @Override public final Void call() throws Exception { targetDtxMgr.unregisterResourceManager(resUuid); return null; } }); } Collections.shuffle(taskList); // executes the tasks and verifies no unusual exceptions are thrown for (final Future<Void> currResult : executor.invokeAll(taskList)) { try { currResult.get(); } catch (final ExecutionException e) { final Throwable cause = e.getCause(); assertNotNull(cause); assertEquals(IllegalArgumentException.class, cause.getClass()); } } } /** * Tests the successful execution of the {@link DtxManager#submit(UUID, byte[])} method. * * @throws XAException * if mock setup fails, not part of this test */ @Test public final void testSubmit() throws XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); final UUID resUuid = UUID.randomUUID(); final DtxResourceManager newResMgr = newResMgrThatDoesEverythingRight(resUuid); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(newResMgr.getId())); final UUID taskId = targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); assertNotNull(taskId); assertFalse(UNKNOWN == targetDtxMgr.getTask(taskId).getStatus()); } /** * Tests the successful execution of the {@link DtxManager#submit(UUID, byte[])} method on a non-up-to-date resource * manager with additional verification of task status updates. * * @throws XAException * if mock setup fails, not part of this test * @throws InterruptedException * if interrupted while waiting, not part of this test */ @Test public final void testSubmitCheckTaskStatus() throws XAException, InterruptedException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); final UUID resUuid = UUID.randomUUID(); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(newResMgr.getId())); final UUID taskId = targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); assertNotNull(taskId); DtxTaskStatus currStatus = targetDtxMgr.getTask(taskId).getStatus(); assertFalse(UNKNOWN == currStatus); int retryCount = TASK_WAIT_RETRY_COUNT; for (; retryCount > 0 && COMMITTED != currStatus && ROLLED_BACK != currStatus; retryCount--) { Thread.sleep(TASK_WAIT_TIME_MS); currStatus = targetDtxMgr.getTask(taskId).getStatus(); } assertTrue(retryCount > 0); assertFalse(DtxConstants.DEFAULT_TIMESTAMP_VALUE == targetDtxMgr.getTaskTimestamp(taskId)); } /** * Tests the {@link DtxManager#submit(UUID, byte[])} method's failure due to not being started. * * @throws XAException * if mock setup fails, not part of this test */ @Test(expected = IllegalStateException.class) public final void testSubmitFailNotStarted() throws XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final UUID resUuid = UUID.randomUUID(); final DtxResourceManager newResMgr = newResMgrThatDoesEverythingRight(resUuid); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(newResMgr.getId())); targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); } /** * Tests the {@link DtxManager#submit(UUID, byte[])} method's failure due to a missing quorum of online peers. * * @throws XAException * if mock setup fails, not part of this test */ @Test(expected = IllegalStateException.class) public final void testSubmitFailNoQuorum() throws XAException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final UUID resUuid = UUID.randomUUID(); final DtxResourceManager newResMgr = newResMgrThatDoesEverythingRight(resUuid); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(newResMgr.getId())); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); targetDtxMgr.registerPeer(new DtxNode(UUID.randomUUID(), new InetSocketAddress("127.0.0.1", DEFAULT_HZ_PORT + 1))); targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); } /** * Tests the {@link DtxManager#submit(UUID, byte[])} method's failure due to a missing quorum of online peers. * * @throws XAException * if mock setup fails, not part of this test * @throws InterruptedException * if interrupted while waiting, not part of this test * @throws TimeoutException * if waiting for the resource manager to be up-to-date times out, not part of this test */ @Test public final void testFailStopFromWithinTransaction() throws XAException, InterruptedException, TimeoutException { LOGGER.info("Executing"); targetDtxMgr.init(); assertEquals(INITIALIZED, targetDtxMgr.getStatus()); final UUID resUuid = UUID.randomUUID(); final DtxResourceManager naughtyResMgr = new DtxResourceManagerBuilder().setId(resUuid) .setPrepare(null, new Answer<Boolean>() { @Override public final Boolean answer(final InvocationOnMock invocation) throws Throwable { targetDtxMgr.stop(); return DefaultPrepareAnswer.doAnswer(invocation); } }).build(); targetDtxMgr.registerResourceManager(naughtyResMgr); assertEquals(naughtyResMgr, targetDtxMgr.getRegisteredResourceManager(naughtyResMgr.getId())); targetDtxMgr.start(); assertEquals(STARTED, targetDtxMgr.getStatus()); awaitStateUpdate(targetDtxMgr, resUuid, UP_TO_DATE); final UUID taskId = targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); DtxTaskStatus currStatus = targetDtxMgr.getTask(taskId).getStatus(); assertFalse(UNKNOWN == currStatus); int retryCount = TASK_WAIT_RETRY_COUNT; for (; retryCount > 0 && ROLLED_BACK != currStatus; retryCount--) { Thread.sleep(TASK_WAIT_TIME_MS); currStatus = targetDtxMgr.getTask(taskId).getStatus(); } assertTrue(retryCount > 0); assertEquals(STARTED, targetDtxMgr.getStatus()); targetDtxMgr.stop(); } /** * Tests reading tasks on startup previously written to a transaction journal. * * @throws XAException * if mock setup fails, not part of this test * @throws IllegalStateException * if initializing or starting fails, not part of this test * @throws IOException * if writing the journal fails, not part of this test * @throws InterruptedException * if interrupted while waiting for resource manager startup, not part of this test * @throws TimeoutException * after timing out on waiting for resource manager startup, not part of this test */ @Test public final void testDtxManagerVerifyTasksAfterRegistration() throws XAException, IllegalStateException, IOException, InterruptedException, TimeoutException { LOGGER.info("Executing"); final UUID resUuid = UUID.randomUUID(); final DtxResourceManager targetResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid); final ArrayList<UUID> writtenTasks = writeTaskToJournal(resUuid); targetDtxMgr.init(); targetDtxMgr.start(); targetDtxMgr.registerResourceManager(targetResMgr); awaitStateUpdate(targetDtxMgr, resUuid, UP_TO_DATE); // Test if task list is complete (using getTasks) final DtxTaskAdm[] currentTasks = targetDtxMgr.getTasks(); assertEquals(writtenTasks.size(), currentTasks.length); for (final DtxTaskAdm task : currentTasks) { assertTrue(writtenTasks.contains(UUID.fromString(task.getTaskId()))); } // Test each task status (using getTask) int i = 0; for (final UUID taskId : writtenTasks) { if (i % 2 == 0) { assertTrue(DtxTaskStatus.ROLLED_BACK == targetDtxMgr.getTask(taskId).getStatus()); assertFalse(DtxConstants.DEFAULT_TIMESTAMP_VALUE == targetDtxMgr.getTaskTimestamp(taskId)); } else { assertTrue(DtxTaskStatus.COMMITTED == targetDtxMgr.getTask(taskId).getStatus()); assertFalse(DtxConstants.DEFAULT_TIMESTAMP_VALUE == targetDtxMgr.getTaskTimestamp(taskId)); } i++; } } /** * Tests reading of the resource manager list. * * @throws XAException * if mock setup fails, not part of this test * @throws InterruptedException * if interrupted while waiting for resource manager startup, not part of this test * @throws TimeoutException * after timing out on waiting for resource manager startup, not part of this test */ @Test public final void testDtxManagerVerifyResourceManagerList() throws XAException, InterruptedException, TimeoutException { LOGGER.info("Executing"); targetDtxMgr.init(); targetDtxMgr.start(); final ArrayList<DtxResourceManager> registerResMgrs = new ArrayList<>(); for (int i = 0; i < NB_OF_RES_MGR; i++) { final UUID resourceId = UUID.randomUUID(); final DtxResourceManager newResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resourceId); assertNotNull(newResMgr); targetDtxMgr.registerResourceManager(newResMgr); awaitStateUpdate(targetDtxMgr, resourceId, UP_TO_DATE); assertEquals(newResMgr, targetDtxMgr.getRegisteredResourceManager(resourceId)); registerResMgrs.add(newResMgr); } final DtxResourceManagerAdm[] resMgrs = targetDtxMgr.getResourceManagers(); assertEquals(NB_OF_RES_MGR, resMgrs.length); boolean ok = false; for (final DtxResourceManager currResMgr : registerResMgrs) { for (final DtxResourceManagerAdm resAdm : resMgrs) { if (resAdm.getUuid().equals(currResMgr.getId().toString())) { assertEquals(UP_TO_DATE, resAdm.getStatus()); assertFalse(resAdm.getJournalPath().equals("")); assertEquals(DtxJournalStatus.STARTED, resAdm.getJournalStatus()); ok = true; } } assertTrue(ok); ok = false; } for (final DtxResourceManager currResMgr : registerResMgrs) { final UUID currId = currResMgr.getId(); targetDtxMgr.unregisterResourceManager(currId); assertNull(targetDtxMgr.getRegisteredResourceManager(currId)); } } /** * Tests reading of the request queue. * * @throws XAException * if mock setup fails, not part of this test * @throws TimeoutException * after timing out on waiting for resource manager startup, not part of this test * @throws InterruptedException * if thread sleep is interrupted, not part of this test * */ @Test public final void testDtxManagerVerifyRequestQueue() throws XAException, InterruptedException, TimeoutException { LOGGER.info("Executing"); final UUID resUuid = UUID.randomUUID(); targetDtxMgr.init(); targetDtxMgr.start(); final DtxResourceManager targetResMgr = new DtxResourceManagerBuilder().setId(resUuid) .setPrepare(null, new Answer<Boolean>() { @Override public final Boolean answer(final InvocationOnMock invocation) throws Throwable { Thread.sleep(TASK_MIN_DURATION_MS); return DefaultPrepareAnswer.doAnswer(invocation); } }).build(); targetDtxMgr.registerResourceManager(targetResMgr); awaitStateUpdate(targetDtxMgr, resUuid, UP_TO_DATE); final UUID firstTaskId = targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); final UUID secondTaskId = targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); targetDtxMgr.submit(resUuid, DEFAULT_PAYLOAD); final DtxRequestQueueAdm queue = targetDtxMgr.getRequestQueue(); assertTrue(2 <= queue.getNbOfPendingRequests()); assertTrue(Lists.newArrayList(firstTaskId.toString(), secondTaskId.toString()).contains(queue.getNextTaskID())); assertEquals(resUuid.toString(), queue.getNextResourceManagerID()); } private final ArrayList<UUID> writeTaskToJournal(final UUID resourceId) throws IOException { final JournalRotationManager fixtureRotMgr = new JournalRotationManager(0); fixtureRotMgr.start(); final File journalDir = dtxConfig.getJournalDirectory().toFile(); if (!journalDir.exists()) { journalDir.mkdirs(); } final String journalFilename = TransactionManager.newJournalFilePrefix(targetDtxMgr.getNodeId(), resourceId); final WritableTxJournal targetJournal = new WritableTxJournal(journalDir, journalFilename, TEST_ROTATION_THRESHOLD, fixtureRotMgr); targetJournal.start(); DtxTestHelper.writeCompleteTransactions(targetJournal, TEST_NB_OF_TEST_ENTRIES, null, DtxTestHelper.newRandomParticipantsSet()); final ArrayList<UUID> tasksList = DtxTestHelper.readCompleteTransactions(targetJournal); fixtureRotMgr.stop(); targetJournal.stop(); return tasksList; } }