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.DtxConstants.DEFAULT_LAST_TX_VALUE;
import static io.eguan.dtx.DtxNodeState.INITIALIZED;
import static io.eguan.dtx.DtxResourceManagerState.UP_TO_DATE;
import static io.eguan.dtx.DtxTestHelper.DEFAULT_TX_MESSAGE;
import static io.eguan.dtx.DtxTestHelper.buildDefaultTransaction;
import static io.eguan.dtx.DtxTestHelper.newDtxManagerConfig;
import static io.eguan.dtx.DtxTestHelper.registerResMgrWithTxMgr;
import static io.eguan.dtx.proto.TxProtobufUtils.fromUuid;
import static io.eguan.dtx.proto.TxProtobufUtils.toUuid;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import io.eguan.dtx.DtxManager;
import io.eguan.dtx.DtxManagerConfig;
import io.eguan.dtx.DtxResourceManager;
import io.eguan.dtx.DtxTaskStatus;
import io.eguan.dtx.TransactionManager;
import io.eguan.dtx.DtxTaskApiAbstract.TaskLoader;
import io.eguan.proto.Common.ProtocolVersion;
import io.eguan.proto.dtx.DistTxWrapper.TxMessage;
import io.eguan.proto.dtx.DistTxWrapper.TxNode;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
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.protobuf.ByteString;
/**
* Test class for {@link TransactionManager}'s methods with a few special error cases.
*
* Protocol and {@link DtxResourceManager}-related error cases are tested separately.
*
* @author oodrive
* @author pwehrle
* @author ebredzinski
*
*/
public final class TestTransactionManager {
private static final Logger LOGGER = LoggerFactory.getLogger(TestTransactionManager.class);
private static final int FUTURE_TIMEOUT_S = 10;
private static final Set<TxNode> PARTICIPANTS = DtxTestHelper.newRandomParticipantsSet();
private static final int NB_OF_TRANSACTIONS = 30;
private DtxManager dtxManager;
private TransactionManager target;
private Path tmpJournalDir;
/**
* Sets up common fixture.
*
* @throws InitializationError
* if setup fails
*/
@Before
public final void setUp() throws InitializationError {
try {
this.tmpJournalDir = Files.createTempDirectory(TestTransactionInitiator.class.getSimpleName());
}
catch (final IOException e) {
throw new InitializationError(e);
}
final DtxManagerConfig dtxConfig = newDtxManagerConfig(tmpJournalDir);
this.dtxManager = new DtxManager(dtxConfig);
dtxManager.init();
assertEquals(INITIALIZED, dtxManager.getStatus());
target = new TransactionManager(dtxConfig, dtxManager);
target.startUp(null);
}
/**
* Tears down common fixture.
*
* @throws InitializationError
* if teardown fails
*/
@After
public final void tearDown() throws InitializationError {
target.shutdown();
dtxManager.fini();
try {
io.eguan.utils.Files.deleteRecursive(tmpJournalDir);
}
catch (final IOException e) {
throw new InitializationError(e);
}
}
/**
* Tests the failure to construct a {@link TransactionManager} due to a <code>null</code> dtx manager configuration.
*
* @throws NullPointerException
* expected for this test
*/
@Test(expected = NullPointerException.class)
public final void testConstructionFailNullDtxManagerConfig() {
new TransactionManager(null, dtxManager);
}
/**
* Tests the failure to construct a {@link TransactionManager} due to a <code>null</code> {@link DtxManager}.
*
* @throws NullPointerException
* expected for this test
*/
@Test(expected = NullPointerException.class)
public final void testConstructionFailNullDtxManager() {
new TransactionManager(newDtxManagerConfig(tmpJournalDir), null);
}
/**
* Tests the successful execution of the {@link TransactionManager#start(TxMessage, Iterable)} method.
*
* @throws XAException
* not part of this test
*/
@Test
public final void testStart() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
target.start(defTx, PARTICIPANTS);
}
/**
* Tests failure of the {@link TransactionManager#start(TxMessage, Iterable)} method due to a bad payload format.
*
* @throws XAException
* expected for this test
*/
@Test(expected = XAException.class)
public final void testStartFailBadPayload() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target,
DtxDummyRmFactory.newResMgrFailingOnStart(null, new XAException(XAException.XAER_INVAL)), null);
final long txId = DtxTestHelper.nextTxId();
final TxMessage badTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resUuid)).setPayload(ByteString.copyFrom(DtxDummyRmFactory.BAD_PAYLOAD))
.build();
try {
target.start(badTx, PARTICIPANTS);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_INVAL, xe.errorCode);
throw xe;
}
}
/**
* Tests failure of the {@link TransactionManager#start(TxMessage, Iterable)} method due to a shut down instance.
*
* @throws XAException
* not part of this test
* @throws IllegalStateException
* if the instance is shut down, expected for this test
*/
@Test(expected = IllegalStateException.class)
public final void testStartFailShutdown() throws XAException, IllegalStateException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
target.shutdown();
assertTrue(target.isShutdown());
target.setResManagerSyncState(resUuid, UP_TO_DATE);
target.start(defTx, PARTICIPANTS);
}
/**
* Tests failure of the {@link TransactionManager#start(TxMessage, Iterable)} method due to an unavailable resource
* manager.
*
* @throws XAException
* expected for this test
* @throws IllegalStateException
* if the instance is shut down, not part of this test
*/
@Test(expected = XAException.class)
public final void testStartFailResMgrUnavailable() throws XAException, IllegalStateException {
final UUID resId = UUID.randomUUID();
assertNull(target.getRegisteredResourceManager(resId));
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resId)).build();
try {
target.start(defTx, PARTICIPANTS);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_RMFAIL, xe.errorCode);
throw xe;
}
}
/**
* Tests the successful execution of the {@link TransactionManager#prepare(long)} method.
*
* @throws XAException
* not part of this test
*/
@Test
public final void testPrepare() throws XAException {
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null);
final UUID resUuid = resMgr.getId();
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resUuid));
target.setResManagerSyncState(resUuid, UP_TO_DATE);
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resMgr.getId())).build();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.commit(txId, PARTICIPANTS);
}
/**
* Tests the successful execution of the {@link TransactionManager#prepare(long)} method while unregistering a
* non-existent resource manager, thus potentially skewing the lock count held by the transaction thread.
*
* @throws XAException
* not part of this test
*/
@Test
public final void testPrepareLockingUnregResMgr() throws XAException {
final UUID resUuid = UUID.randomUUID();
final TransactionManager txMgr = target;
final DtxResourceManager resMgr = new DtxDummyRmFactory.DtxResourceManagerBuilder().setId(resUuid)
.setPrepare(null, new Answer<Boolean>() {
@Override
public final Boolean answer(final InvocationOnMock invocation) throws Throwable {
final boolean answer = DtxDummyRmFactory.DefaultPrepareAnswer.doAnswer(invocation)
.booleanValue();
final boolean preHold = txMgr.holdsTransactionLock();
final UUID newResId = UUID.randomUUID();
txMgr.unregisterResourceManager(newResId);
return Boolean.valueOf(answer && preHold && txMgr.holdsTransactionLock());
}
}).build();
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resUuid));
target.setResManagerSyncState(resUuid, UP_TO_DATE);
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resMgr.getId())).build();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.commit(txId, PARTICIPANTS);
}
/**
* Tests the {@link TransactionManager#prepare(long)} method's failure while registering an already registered
* resource manager, verifying there is no skew in the lock count held by the transaction thread.
*
* @throws XAException
* not part of this test
* @throws IllegalArgumentException
* expected for this test
*/
@Test(expected = IllegalArgumentException.class)
public final void testPrepareLockingRegResMgr() throws XAException, IllegalArgumentException {
final UUID resUuid = UUID.randomUUID();
final TransactionManager txMgr = target;
final DtxResourceManager resMgr = new DtxDummyRmFactory.DtxResourceManagerBuilder().setId(resUuid)
.setPrepare(null, new Answer<Boolean>() {
@Override
public final Boolean answer(final InvocationOnMock invocation) throws Throwable {
final boolean answer = DtxDummyRmFactory.DefaultPrepareAnswer.doAnswer(invocation)
.booleanValue();
final boolean preHold = txMgr.holdsTransactionLock();
final DtxResourceManager newResId = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid);
txMgr.registerResourceManager(newResId, null);
return Boolean.valueOf(answer && preHold && txMgr.holdsTransactionLock());
}
}).build();
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resUuid));
target.setResManagerSyncState(resUuid, UP_TO_DATE);
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resMgr.getId())).build();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
}
/**
* Tests the {@link TransactionManager#prepare(long)} method's failure on trying to
* {@link TransactionManager#shutdown()} the {@link TransactionManager} from within a transaction.
*
* @throws XAException
* not part of this test
* @throws IllegalThreadStateException
* if trying to alter the {@link TransactionManager}'s state from within a transaction fails, expected
* for this test
*/
@Test(expected = IllegalThreadStateException.class)
public final void testPrepareFailToShutdown() throws XAException, IllegalThreadStateException {
final UUID resUuid = UUID.randomUUID();
final TransactionManager txMgr = target;
final DtxResourceManager resMgr = new DtxDummyRmFactory.DtxResourceManagerBuilder().setId(resUuid)
.setPrepare(null, new Answer<Boolean>() {
@Override
public final Boolean answer(final InvocationOnMock invocation) throws Throwable {
assertTrue(DtxDummyRmFactory.DefaultPrepareAnswer.doAnswer(invocation).booleanValue());
assertTrue(txMgr.holdsTransactionLock());
txMgr.shutdown();
return Boolean.FALSE;
}
}).build();
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resUuid));
target.setResManagerSyncState(resUuid, UP_TO_DATE);
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resMgr.getId())).build();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
}
/**
* Tests the {@link TransactionManager#prepare(long)} method's failure on trying to startup the
* {@link TransactionManager} from within a transaction.
*
* @throws XAException
* not part of this test
* @throws IllegalThreadStateException
* if trying to alter the {@link TransactionManager}'s state from within a transaction fails, expected
* for this test
*/
@Test(expected = IllegalThreadStateException.class)
public final void testPrepareFailToStartUp() throws XAException, IllegalThreadStateException {
final UUID resUuid = UUID.randomUUID();
final TransactionManager txMgr = target;
final DtxResourceManager resMgr = new DtxDummyRmFactory.DtxResourceManagerBuilder().setId(resUuid)
.setPrepare(null, new Answer<Boolean>() {
@Override
public final Boolean answer(final InvocationOnMock invocation) throws Throwable {
assertTrue(DtxDummyRmFactory.DefaultPrepareAnswer.doAnswer(invocation).booleanValue());
assertTrue(txMgr.holdsTransactionLock());
txMgr.startUp(null);
return Boolean.FALSE;
}
}).build();
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resUuid));
target.setResManagerSyncState(resUuid, UP_TO_DATE);
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resMgr.getId())).build();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
}
/**
* Tests failure of the {@link TransactionManager#prepare(long)} method due to a shut down instance.
*
* @throws XAException
* not part of this test
* @throws IllegalStateException
* if the instance is shut down, expected for this test
*/
@Test(expected = IllegalStateException.class)
public final void testPrepareFailShutdown() throws XAException, IllegalStateException {
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null);
final UUID resUuid = resMgr.getId();
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resUuid));
target.setResManagerSyncState(resUuid, UP_TO_DATE);
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resUuid)).build();
target.start(defTx, PARTICIPANTS);
target.shutdown();
assertTrue(target.isShutdown());
target.setResManagerSyncState(resUuid, UP_TO_DATE);
target.prepare(txId);
}
/**
* Tests failure of the {@link TransactionManager#prepare(long)} method due to an unavailable resource manager.
*
* @throws XAException
* expected for this test
* @throws IllegalStateException
* if the instance is shut down, not part of this test
*/
@Test(expected = XAException.class)
public final void testPrepareFailResMgrUnavailable() throws XAException, IllegalStateException {
final UUID resUuid = UUID.randomUUID();
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid);
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resUuid));
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resUuid)).build();
target.start(defTx, PARTICIPANTS);
target.unregisterResourceManager(resUuid);
assertNull(target.getRegisteredResourceManager(resUuid));
try {
target.prepare(txId);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_RMFAIL, xe.errorCode);
throw xe;
}
}
/**
* Tests the successful execution of the {@link TransactionManager#commit(long)} method.
*
* @throws XAException
* not part of this test
*/
@Test
public final void testCommit() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
final long txId = defTx.getTxId();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.commit(txId, PARTICIPANTS);
}
/**
* Tests failure of the {@link TransactionManager#commit(long)} method due to a shut down instance.
*
* @throws XAException
* not part of this test
* @throws IllegalStateException
* if the instance is shut down, expected for this test
*/
@Test(expected = IllegalStateException.class)
public final void testCommitFailShutdown() throws XAException, IllegalStateException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
final long txId = defTx.getTxId();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.shutdown();
assertTrue(target.isShutdown());
target.setResManagerSyncState(resUuid, UP_TO_DATE);
target.commit(txId, PARTICIPANTS);
}
/**
* Tests failure of the {@link TransactionManager#commit(long)} method due to an unavailable resource manager.
*
* @throws XAException
* expected for this test
* @throws IllegalStateException
* if the instance is shut down, not part of this test
*/
@Test(expected = XAException.class)
public final void testCommitFailResMgrUnavailable() throws XAException, IllegalStateException {
final UUID resId = UUID.randomUUID();
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resId);
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resId));
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resId)).build();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.unregisterResourceManager(resId);
assertNull(target.getRegisteredResourceManager(resId));
try {
target.commit(txId, PARTICIPANTS);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_RMFAIL, xe.errorCode);
throw xe;
}
}
/**
* Tests the successful execution of the {@link TransactionManager#rollback(long)} method on a started transaction.
*
* @throws XAException
* not part of this test
*/
@Test
public final void testRollbackStarted() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
final long txId = defTx.getTxId();
target.start(defTx, PARTICIPANTS);
target.rollback(txId, PARTICIPANTS);
}
/**
* Tests failure of the {@link TransactionManager#rollback(long)} method on a started transaction of a shut down
* instance.
*
* @throws XAException
* not part of this test
* @throws IllegalStateException
* if the instance is shut down, expected for this test
*/
@Test(expected = IllegalStateException.class)
public final void testRollbackStartedFailShutdown() throws XAException, IllegalStateException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
final long txId = defTx.getTxId();
target.setResManagerSyncState(resUuid, UP_TO_DATE);
target.start(defTx, PARTICIPANTS);
target.shutdown();
assertTrue(target.isShutdown());
target.setResManagerSyncState(resUuid, UP_TO_DATE);
target.rollback(txId, PARTICIPANTS);
}
/**
* Tests the successful execution of the {@link TransactionManager#rollback(long)} method on a prepared transaction.
*
* @throws XAException
* not part of this test
*/
@Test
public final void testRollbackPrepared() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
final long txId = defTx.getTxId();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.rollback(txId, PARTICIPANTS);
}
/**
* Tests failure of the {@link TransactionManager#rollback(long)} method on a prepared transaction of a shut down
* instance.
*
* @throws XAException
* not part of this test
* @throws IllegalStateException
* if the instance is shut down, expected for this test
*/
@Test(expected = IllegalStateException.class)
public final void testRollbackPreparedFailShutdown() throws XAException, IllegalStateException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final TxMessage defTx = buildDefaultTransaction(resUuid);
final long txId = defTx.getTxId();
target.setResManagerSyncState(resUuid, UP_TO_DATE);
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.shutdown();
assertTrue(target.isShutdown());
target.setResManagerSyncState(resUuid, UP_TO_DATE);
target.rollback(txId, PARTICIPANTS);
}
/**
* Tests failure of the {@link TransactionManager#rollback(long)} method due to an unavailable resource manager.
*
* @throws XAException
* expected for this test
* @throws IllegalStateException
* if the instance is shut down, not part of this test
*/
@Test(expected = XAException.class)
public final void testRollbackFailResMgrUnavailable() throws XAException, IllegalStateException {
final UUID resId = UUID.randomUUID();
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resId);
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resId));
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resId)).build();
target.start(defTx, PARTICIPANTS);
assertTrue(target.prepare(txId).booleanValue());
target.unregisterResourceManager(resId);
assertNull(target.getRegisteredResourceManager(resId));
try {
target.rollback(txId, PARTICIPANTS);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_RMFAIL, xe.errorCode);
throw xe;
}
}
/**
* Tests failure of the {@link TransactionManager#rollback(long)} method due to an resource manager having
* unregistered itself during prepare.
*
* Note: this simply reproduces a deadlock situation if the {@link TransactionManager} does not allow un/registering
* during transaction phase execution. While this would seem like sane behavior, in practice un/registration must be
* able to proceed regardless of any currently executing transactions, as unregistering does not prevent running
* transaction phases from proceeding.
*
* @throws XAException
* expected for this test
* @throws TimeoutException
* if the prepare operation times out, considered a test failure
* @throws ExecutionException
* if the prepare operation execution fails, considered a test failure
* @throws InterruptedException
* if the thread executing the prepare operation is interrupted, not part of this test
* @throws IllegalStateException
* if the instance is shut down, not part of this test
*/
@Test(expected = XAException.class)
public final void testInTransactionResMgrRegistrations() throws XAException, InterruptedException,
ExecutionException, TimeoutException {
final UUID resId = UUID.randomUUID();
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrUnregisteringOnPrepare(resId, target);
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resId));
final long txId = DtxTestHelper.nextTxId();
final TxMessage defTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(txId).setResId(toUuid(resId)).build();
target.start(defTx, PARTICIPANTS);
final ExecutorService executor = Executors.newFixedThreadPool(1);
final Future<Boolean> prepFuture = executor.submit(new Callable<Boolean>() {
@Override
public final Boolean call() throws Exception {
return target.prepare(txId);
}
});
try {
final boolean result = prepFuture.get(FUTURE_TIMEOUT_S, TimeUnit.SECONDS).booleanValue();
assertTrue(result);
}
finally {
// this only works if locks are acquired in an interruptible way
executor.shutdownNow();
}
assertNull(target.getRegisteredResourceManager(resId));
try {
target.rollback(txId, PARTICIPANTS);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_RMFAIL, xe.errorCode);
throw xe;
}
}
/**
* Tests the execution of {@value #NB_OF_TRANSACTIONS} transactions out of order in the start phase, then in order
* for the rest.
*
* @throws XAException
* if mock setup fails, not part of this test
*/
@Test
public final void testMultipleTxInOrder() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
// create a strictly growing sequence of transaction IDs
final ArrayList<Long> txIdList = new ArrayList<Long>(NB_OF_TRANSACTIONS);
for (int i = 0; i < NB_OF_TRANSACTIONS; i++) {
txIdList.add(Long.valueOf(i ^ 2 + 1));
}
// starts all transactions in a random order
Collections.shuffle(txIdList);
for (final Long currTxId : txIdList) {
target.start(
TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(currTxId.longValue()).setResId(toUuid(resUuid)).build(), PARTICIPANTS);
}
// prepares and commits/rolls back in order
Collections.sort(txIdList);
for (final Long currTxId : txIdList) {
final long txId = currTxId.longValue();
assertEquals(Boolean.TRUE, target.prepare(txId));
if (txId % 2 == 0) {
target.commit(txId, PARTICIPANTS);
}
else {
target.rollback(txId, PARTICIPANTS);
}
}
}
/**
* Tests the successful (and repeated) execution of the
* {@link TransactionManager#getLastCompleteTxIdForResMgr(UUID)} method.
*
* @throws IllegalStateException
* if the [@link TransactionManager} is not started, not part of this test
* @throws XAException
* if construction of mock {@link DtxResourceManager}s fails, not part of this test
*/
@Test
public final void testGetLastCompleteTxId() throws XAException {
assertFalse(target.isShutdown());
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
// inits and writes the first 'last transaction'
long lastTxId = DtxTestHelper.nextTxId();
target.start(TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1).setTxId(lastTxId)
.setResId(toUuid(resUuid)).build(), PARTICIPANTS);
assertEquals(DEFAULT_LAST_TX_VALUE, target.getLastCompleteTxIdForResMgr(resUuid));
assertTrue(target.prepare(lastTxId).booleanValue());
assertEquals(DEFAULT_LAST_TX_VALUE, target.getLastCompleteTxIdForResMgr(resUuid));
target.commit(lastTxId, PARTICIPANTS);
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
// writes more partial and complete transactions
for (int i = 0; i < NB_OF_TRANSACTIONS; i++) {
final long nextTxId = DtxTestHelper.nextTxId();
// writes start and checks if the old value's still returned
target.start(
TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1).setTxId(nextTxId)
.setResId(toUuid(resUuid)).build(), PARTICIPANTS);
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
assertTrue(target.prepare(nextTxId).booleanValue());
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
// checks if the next ID is returned after completing the transaction
if (nextTxId % 2 == 0) {
target.commit(nextTxId, PARTICIPANTS);
}
else {
target.rollback(nextTxId, PARTICIPANTS);
}
assertEquals(nextTxId, target.getLastCompleteTxIdForResMgr(resUuid));
lastTxId = nextTxId;
}
}
/**
* Tests the possibility to call the {@link TransactionManager#start(TxMessage, Iterable)} with out-of-order
* transaction IDs.
*
* @throws XAException
* if mock setup fails, not part of this test
*/
@Test
public final void testStartOutOfOrder() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
final long firstTxId = DtxTestHelper.nextTxId();
final long secondTxId = DtxTestHelper.nextTxId();
final TxMessage firstTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(firstTxId).setResId(toUuid(resUuid)).build();
final TxMessage secondTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(secondTxId).setResId(toUuid(resUuid)).build();
target.start(secondTx, PARTICIPANTS);
target.start(firstTx, PARTICIPANTS);
assertEquals(Boolean.TRUE, target.prepare(firstTxId));
target.commit(firstTxId, PARTICIPANTS);
assertEquals(Boolean.TRUE, target.prepare(secondTxId));
target.commit(secondTxId, PARTICIPANTS);
}
/**
* Tests the ability to read a given task in the journal with different status.
*
* @throws XAException
* if mock setup fails, not part of this test
*/
@Test
public final void testReadTask() throws XAException {
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
// Create a new task with status committed
long txId = DtxTestHelper.nextTxId();
TxMessage tx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1).setTxId(txId)
.setResId(toUuid(resUuid)).setTaskId(toUuid(UUID.randomUUID())).build();
target.start(tx, PARTICIPANTS);
assertEquals(Boolean.TRUE, target.prepare(txId));
target.commit(txId, PARTICIPANTS);
UUID taskId = fromUuid(tx.getTaskId());
TaskLoader task = target.readTask(taskId);
assertEquals(taskId, UUID.fromString(task.getDtxTaskAdm().getTaskId()));
assertEquals(DtxTaskStatus.COMMITTED, task.getDtxTaskAdm().getStatus());
assertEquals(resUuid, UUID.fromString(task.getDtxTaskAdm().getResourceId()));
// Create a new task with status rollback
txId = DtxTestHelper.nextTxId();
tx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1).setTxId(txId)
.setResId(toUuid(resUuid)).setTaskId(toUuid(UUID.randomUUID())).build();
target.start(tx, PARTICIPANTS);
assertEquals(Boolean.TRUE, target.prepare(txId));
target.rollback(txId, PARTICIPANTS);
taskId = fromUuid(tx.getTaskId());
task = target.readTask(taskId);
assertEquals(taskId, UUID.fromString(task.getDtxTaskAdm().getTaskId()));
assertEquals(DtxTaskStatus.ROLLED_BACK, task.getDtxTaskAdm().getStatus());
assertEquals(resUuid, UUID.fromString(task.getDtxTaskAdm().getResourceId()));
// Create a new task with status started
txId = DtxTestHelper.nextTxId();
tx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1).setTxId(txId)
.setResId(toUuid(resUuid)).setTaskId(toUuid(UUID.randomUUID())).build();
target.start(tx, PARTICIPANTS);
assertEquals(Boolean.TRUE, target.prepare(txId));
taskId = fromUuid(tx.getTaskId());
task = target.readTask(taskId);
assertEquals(taskId, UUID.fromString(task.getDtxTaskAdm().getTaskId()));
assertEquals(DtxTaskStatus.STARTED, task.getDtxTaskAdm().getStatus());
assertEquals(resUuid, UUID.fromString(task.getDtxTaskAdm().getResourceId()));
}
/**
* Tests the {@link TransactionManager#prepare(long)} method's failure before the preceding transaction has been
* committed.
*
* @throws XAException
* if mock setup fails, not part of this test
*/
@Test(expected = XAException.class)
public final void testPrepareFailBeforePrecedingCommit() throws XAException {
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null);
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resMgr.getId()));
final long firstTxId = DtxTestHelper.nextTxId();
final long secondTxId = DtxTestHelper.nextTxId();
final TxMessage firstTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(firstTxId).setResId(toUuid(resMgr.getId())).build();
final TxMessage secondTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(secondTxId).setResId(toUuid(resMgr.getId())).build();
target.start(firstTx, PARTICIPANTS);
target.start(secondTx, PARTICIPANTS);
assertEquals(Boolean.TRUE, target.prepare(firstTxId));
try {
target.prepare(secondTxId);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_PROTO, xe.errorCode);
throw xe;
}
}
/**
* Tests the successful repeated execution of the {@link TransactionManager#getLastCompleteTxIdForResMgr(UUID)}
* method, shutting down the {@link TransactionManager} in between invocations.
*
* @throws IllegalStateException
* if the [@link TransactionManager} is not started, not part of this test
* @throws XAException
* if construction of mock {@link DtxResourceManager}s fails, not part of this test
*/
@Test
public final void testGetLastCompleteTxIdAfterRestart() throws XAException {
assertFalse(target.isShutdown());
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
// inits and writes one 'last transaction'
long lastTxId = DtxTestHelper.nextTxId();
target.start(TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1).setTxId(lastTxId)
.setResId(toUuid(resUuid)).build(), PARTICIPANTS);
assertEquals(DEFAULT_LAST_TX_VALUE, target.getLastCompleteTxIdForResMgr(resUuid));
assertTrue(target.prepare(lastTxId).booleanValue());
assertEquals(DEFAULT_LAST_TX_VALUE, target.getLastCompleteTxIdForResMgr(resUuid));
target.commit(lastTxId, PARTICIPANTS);
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
// writes more partial and complete transactions
for (int i = 0; i < NB_OF_TRANSACTIONS; i++) {
final long nextTxId = DtxTestHelper.nextTxId();
try {
// writes start and checks if the old value's still returned
target.start(
TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(nextTxId).setResId(toUuid(resUuid)).build(), PARTICIPANTS);
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
assertTrue(target.prepare(nextTxId).booleanValue());
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
}
catch (final XAException xe) {
LOGGER.error("XAException during first transaction phase; code=" + xe.errorCode);
throw xe;
}
// shuts down and starts up again
target.shutdown();
assertTrue(target.isShutdown());
target.startUp(null);
assertFalse(target.isShutdown());
target.setResManagerSyncState(resUuid, UP_TO_DATE);
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
// checks if the next ID is returned after completing the transaction
try {
if (nextTxId % 2 == 0) {
target.commit(nextTxId, PARTICIPANTS);
}
else {
target.rollback(nextTxId, PARTICIPANTS);
}
}
catch (final XAException xe) {
LOGGER.error("XAException while completing transaction; code=" + xe.errorCode);
throw xe;
}
assertEquals(nextTxId, target.getLastCompleteTxIdForResMgr(resUuid));
// shuts down and starts up again
target.shutdown();
assertTrue(target.isShutdown());
target.startUp(null);
assertFalse(target.isShutdown());
target.setResManagerSyncState(resUuid, UP_TO_DATE);
assertEquals(nextTxId, target.getLastCompleteTxIdForResMgr(resUuid));
lastTxId = nextTxId;
}
}
/**
* Tests the {@link TransactionManager#prepare(long)} method's failure after a following transaction was prepared.
*
* @throws XAException
* if mock setup fails, not part of this test
*/
@Test(expected = XAException.class)
public final void testPrepareFailAfterFollowingPrepared() throws XAException {
final DtxResourceManager resMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(null);
target.registerResourceManager(resMgr, null);
assertEquals(resMgr, target.getRegisteredResourceManager(resMgr.getId()));
final long firstTxId = DtxTestHelper.nextTxId();
final long secondTxId = DtxTestHelper.nextTxId();
final TxMessage firstTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(firstTxId).setResId(toUuid(resMgr.getId())).build();
final TxMessage secondTx = TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1)
.setTxId(secondTxId).setResId(toUuid(resMgr.getId())).build();
target.start(firstTx, PARTICIPANTS);
target.start(secondTx, PARTICIPANTS);
assertEquals(Boolean.TRUE, target.prepare(secondTxId));
try {
target.prepare(firstTxId);
}
catch (final XAException xe) {
assertEquals(XAException.XAER_PROTO, xe.errorCode);
throw xe;
}
}
/**
* Tests the {@link TransactionManager#getLastCompleteTxIdForResMgr(UUID)} method's failure due to a non-started
* state.
*
* @throws IllegalStateException
* expected for this test
* @throws XAException
* if construction of mock {@link DtxResourceManager}s fails, not part of this test
*/
@Test(expected = IllegalStateException.class)
public final void testGetLastCompleteTxIdFailNotStarted() throws XAException {
assertFalse(target.isShutdown());
final UUID resUuid = registerResMgrWithTxMgr(target, null, null);
// inits and writes one 'last transaction'
final long lastTxId = DtxTestHelper.nextTxId();
target.start(TxMessage.newBuilder(DEFAULT_TX_MESSAGE).setVersion(ProtocolVersion.VERSION_1).setTxId(lastTxId)
.setResId(toUuid(resUuid)).build(), PARTICIPANTS);
assertEquals(DEFAULT_LAST_TX_VALUE, target.getLastCompleteTxIdForResMgr(resUuid));
assertTrue(target.prepare(lastTxId).booleanValue());
assertEquals(DEFAULT_LAST_TX_VALUE, target.getLastCompleteTxIdForResMgr(resUuid));
target.commit(lastTxId, PARTICIPANTS);
assertEquals(lastTxId, target.getLastCompleteTxIdForResMgr(resUuid));
target.shutdown();
assertTrue(target.isShutdown());
target.getLastCompleteTxIdForResMgr(resUuid);
}
}