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.DtxDummyRmFactory.DEFAULT_PAYLOAD;
import static io.eguan.dtx.DtxDummyRmFactory.newResMgrThatDoesEverythingRight;
import static io.eguan.dtx.DtxResourceManagerState.UP_TO_DATE;
import static io.eguan.dtx.DtxTaskStatus.COMMITTED;
import static io.eguan.dtx.DtxTaskStatus.PENDING;
import static io.eguan.dtx.DtxTestHelper.awaitStateUpdate;
import static org.junit.Assert.assertEquals;
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.TransactionManager;
import io.eguan.dtx.journal.JournalRotationManager;
import io.eguan.dtx.journal.WritableTxJournal;
import java.io.File;
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.HashMap;
import java.util.UUID;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests for the initialization phase of the DtxManager, including journal recovery for resource managers.
*
* @author oodrive
* @author pwehrle
*
*/
public final class TestDtxManagerInitL {
private static final Logger LOGGER = LoggerFactory.getLogger(TestDtxManagerInitL.class);
private static final int NB_OF_SHUTDOWN_LOOPS = 5;
private static final int NB_OF_RESSOURCE_MANAGERS = 4;
private static final int TX_WAIT_RETRIES = 5;
private static final int TX_WAIT_MS = 100;
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 Path tmpJournalDir;
private DtxManager dtxManager;
private JournalRotationManager fixtureRotMgr;
private DtxManagerConfig dtxConfig;
/**
* Sets up common fixture.
*
* @throws InitializationError
* if creating temporary directories fails
*/
@Before
public final void setUp() throws InitializationError {
try {
tmpJournalDir = Files.createTempDirectory(TestDtxManager.class.getSimpleName());
}
catch (final IOException e) {
throw new InitializationError(e);
}
fixtureRotMgr = new JournalRotationManager(0);
fixtureRotMgr.start();
this.dtxConfig = DtxTestHelper.newDtxManagerConfig(tmpJournalDir);
this.dtxManager = new DtxManager(dtxConfig);
}
/**
* Tears down common fixture.
*
* @throws InitializationError
* if removing temporary files fails
*/
@After
public final void tearDown() throws InitializationError {
dtxManager.fini();
fixtureRotMgr.stop();
try {
io.eguan.utils.Files.deleteRecursive(tmpJournalDir);
}
catch (final IOException e) {
throw new InitializationError(e);
}
}
/**
* Tests the initialization with repeated start/stop cycles for a single {@link DtxManager} instance with one
* existing journal.
*
* @throws XAException
* if mock setup fails, not part of this test
* @throws IllegalStateException
* if starting and/or writing fails, not part of this test
* @throws IOException
* if any I/O from/to disk fails, not part of this test
* @throws InterruptedException
* if waiting for the {@link DtxManager}'s start times out, not part of this test
* @throws TimeoutException
* if waiting on the {@link DtxResourceManager}s sync state times out, not part of this test
*/
@Test
public final void testDtxManagerInitOneExistingJournal() throws XAException, IllegalStateException, IOException,
InterruptedException, TimeoutException {
LOGGER.info("Executing");
final UUID resUuid = UUID.randomUUID();
final DtxResourceManager targetResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid);
dtxManager.init();
for (int i = 0; i < NB_OF_SHUTDOWN_LOOPS; i++) {
final long lastTxId = writeTxToJournal(resUuid);
dtxManager.registerResourceManager(targetResMgr);
awaitStateUpdate(dtxManager, resUuid, UP_TO_DATE);
try {
assertEquals(lastTxId, dtxManager.getLastCompleteTxIdForResMgr(resUuid));
dtxManager.unregisterResourceManager(resUuid);
dtxManager.registerResourceManager(targetResMgr);
assertEquals(lastTxId, dtxManager.getLastCompleteTxIdForResMgr(resUuid));
dtxManager.unregisterResourceManager(resUuid);
}
finally {
dtxManager.stop();
}
}
}
/**
* Tests the initialization with repeated start/stop cycles for a single {@link DtxManager} instance, with added
* transaction executions and verification of the updated transaction ID after each cycle.
*
* @throws XAException
* if mock setup fails, not part of this test
* @throws IllegalStateException
* if starting and/or writing fails, not part of this test
* @throws IOException
* if any I/O from/to disk fails, not part of this test
* @throws InterruptedException
* if waiting for the {@link DtxManager}'s start times out, not part of this test
* @throws TimeoutException
* if waiting on the {@link DtxResourceManager}s sync state times out, not part of this test
*/
@Test
public final void testDtxManagerInitVerifyTxIdAfterRegistration() throws XAException, IllegalStateException,
IOException, InterruptedException, TimeoutException {
LOGGER.info("Executing");
final UUID resUuid = UUID.randomUUID();
final DtxResourceManager targetResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid);
dtxManager.init();
for (int i = 1; i <= NB_OF_SHUTDOWN_LOOPS; i++) {
final long lastTxId = writeTxToJournal(resUuid);
dtxManager.registerResourceManager(targetResMgr);
awaitStateUpdate(dtxManager, resUuid, UP_TO_DATE);
try {
assertEquals(lastTxId, dtxManager.getLastCompleteTxIdForResMgr(resUuid));
final UUID taskId = dtxManager.submit(resUuid, DtxDummyRmFactory.DEFAULT_PAYLOAD);
DtxMockUtils.verifySuccessfulTxExecution(targetResMgr, i);
int retryCount = 0;
while (!COMMITTED.equals(dtxManager.getTask(taskId).getStatus()) && retryCount < TX_WAIT_RETRIES) {
try {
Thread.sleep(TX_WAIT_MS);
}
catch (final InterruptedException e) {
LOGGER.warn("Interrupted during sleep");
}
retryCount++;
}
assertEquals(lastTxId + 1, dtxManager.getLastCompleteTxIdForResMgr(resUuid));
dtxManager.unregisterResourceManager(resUuid);
}
finally {
dtxManager.stop();
}
}
}
/**
* Tests the initialization with repeated start/stop cycles for a single {@link DtxManager} instance, with added
* transaction executions and verifies merging of the modified resource manager's state with running transactions.
*
* @throws XAException
* if mock setup fails, not part of this test
* @throws IllegalStateException
* if the target resource manager enters an invalid state, not part of this test
* @throws IOException
* if any I/O from/to disk fails, not part of this test
* @throws InterruptedException
* if waiting for the {@link DtxManager}'s start times out, not part of this test
* @throws TimeoutException
* if waiting on the {@link DtxResourceManager}s sync state times out, not part of this test
*/
@Test
public final void testDtxManagerInitMergeLastTxId() throws XAException, IllegalStateException, IOException,
InterruptedException, TimeoutException {
LOGGER.info("Executing");
final UUID resUuid = UUID.randomUUID();
final DtxResourceManager targetResMgr = DtxDummyRmFactory.newResMgrThatDoesEverythingRight(resUuid);
dtxManager.init();
dtxManager.registerResourceManager(targetResMgr);
awaitStateUpdate(dtxManager, resUuid, UP_TO_DATE);
long lastTxId = DEFAULT_LAST_TX_VALUE;
try {
for (int i = 1; i <= NB_OF_SHUTDOWN_LOOPS; i++) {
assertEquals(lastTxId, dtxManager.getLastCompleteTxIdForResMgr(resUuid));
final UUID taskId = dtxManager.submit(resUuid, DEFAULT_PAYLOAD);
DtxMockUtils.verifySuccessfulTxExecution(targetResMgr, i);
int retryCount = 0;
while (!COMMITTED.equals(dtxManager.getTask(taskId).getStatus()) && retryCount < TX_WAIT_RETRIES) {
try {
Thread.sleep(TX_WAIT_MS);
}
catch (final InterruptedException e) {
LOGGER.warn("Interrupted during sleep");
}
retryCount++;
}
lastTxId = dtxManager.getLastCompleteTxIdForResMgr(resUuid);
}
dtxManager.unregisterResourceManager(resUuid);
// write more to the journal
lastTxId = writeTxToJournal(resUuid);
// submit some transactions to have them execute while we try to merge
for (int i = 1; i <= NB_OF_SHUTDOWN_LOOPS; i++) {
final UUID taskId = dtxManager.submit(resUuid, DEFAULT_PAYLOAD);
int retryCount = 0;
while (PENDING != dtxManager.getTask(taskId).getStatus() && retryCount < TX_WAIT_RETRIES) {
try {
Thread.sleep(TX_WAIT_MS);
}
catch (final InterruptedException e) {
LOGGER.warn("Interrupted during sleep");
}
retryCount++;
}
}
dtxManager.registerResourceManager(targetResMgr);
assertTrue(lastTxId <= dtxManager.getLastCompleteTxIdForResMgr(resUuid));
}
finally {
dtxManager.stop();
}
}
/**
* Tests the initialization of a single {@link DtxManager} instance with multiple existing resource manager
* journals.
*
* @throws XAException
* if mock setup fails, not part of this test
* @throws IllegalStateException
* , if starting and/or writing fails, not part of this test
* @throws IOException
* if any I/O from/to disk fails, not part of this test
* @throws InterruptedException
* if waiting for the {@link DtxManager}'s start times out, not part of this test
* @throws TimeoutException
* if waiting on the {@link DtxResourceManager}s sync state times out, not part of this test
*/
@Test
public final void testDtxManagerInitMultipleExistingJournals() throws XAException, IllegalStateException,
IOException, InterruptedException, TimeoutException {
LOGGER.info("Executing");
final ArrayList<DtxResourceManager> resourceMgrs = new ArrayList<DtxResourceManager>(NB_OF_RESSOURCE_MANAGERS);
for (int i = 0; i < NB_OF_RESSOURCE_MANAGERS; i++) {
resourceMgrs.add(newResMgrThatDoesEverythingRight(null));
}
final HashMap<UUID, Long> lastTxIds = new HashMap<UUID, Long>(NB_OF_RESSOURCE_MANAGERS);
long maxWrittenTxId = 0;
for (final DtxResourceManager currResMgr : resourceMgrs) {
final long lastWrittenTxId = writeTxToJournal(currResMgr.getId());
maxWrittenTxId = Math.max(maxWrittenTxId, lastWrittenTxId);
lastTxIds.put(currResMgr.getId(), Long.valueOf(lastWrittenTxId));
}
Collections.shuffle(resourceMgrs);
dtxManager.init();
for (final DtxResourceManager currResMgr : resourceMgrs) {
dtxManager.registerResourceManager(currResMgr);
awaitStateUpdate(dtxManager, currResMgr.getId(), UP_TO_DATE);
}
try {
long maxReadTxId = 0;
for (final DtxResourceManager currResMgr : resourceMgrs) {
final UUID resUuid = currResMgr.getId();
final long lastTxId = lastTxIds.get(currResMgr.getId()).longValue();
assertEquals(lastTxId, dtxManager.getLastCompleteTxIdForResMgr(resUuid));
dtxManager.unregisterResourceManager(resUuid);
dtxManager.registerResourceManager(currResMgr);
final long lastCompleteTxId = dtxManager.getLastCompleteTxIdForResMgr(resUuid);
assertEquals(lastTxId, lastCompleteTxId);
maxReadTxId = Math.max(maxReadTxId, lastCompleteTxId);
}
assertEquals(maxWrittenTxId, maxReadTxId);
}
finally {
dtxManager.stop();
}
}
// TODO: add tests for error cases and initialization states
private final long writeTxToJournal(final UUID resourceId) throws IOException {
if (!fixtureRotMgr.isStarted()) {
fixtureRotMgr.start();
}
final String journalFilename = TransactionManager.newJournalFilePrefix(dtxManager.getNodeId(), resourceId);
final File journalDir = dtxConfig.getJournalDirectory().toFile();
if (!journalDir.exists()) {
journalDir.mkdirs();
}
final WritableTxJournal targetJournal = new WritableTxJournal(journalDir, journalFilename,
TEST_ROTATION_THRESHOLD, fixtureRotMgr);
targetJournal.start();
final long lastTxId = DtxTestHelper.writeCompleteTransactions(targetJournal, TEST_NB_OF_TEST_ENTRIES, null,
DtxTestHelper.newRandomParticipantsSet());
fixtureRotMgr.stop();
targetJournal.stop();
return lastTxId;
}
}