/*
* Copyright © 2012-2014 Cask Data, Inc.
*
* 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.
*/
package co.cask.tephra.persist;
import co.cask.tephra.ChangeId;
import co.cask.tephra.TransactionManager;
import co.cask.tephra.TransactionType;
import co.cask.tephra.TxConstants;
import co.cask.tephra.metrics.TxMetricsCollector;
import co.cask.tephra.snapshot.DefaultSnapshotCodec;
import co.cask.tephra.snapshot.SnapshotCodecProvider;
import co.cask.tephra.snapshot.SnapshotCodecV4;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.hadoop.conf.Configuration;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Runs transaction persistence tests against the {@link LocalFileTransactionStateStorage} and
* {@link LocalFileTransactionLog} implementations.
*/
public class LocalTransactionStateStorageTest extends AbstractTransactionStateStorageTest {
@ClassRule
public static TemporaryFolder tmpDir = new TemporaryFolder();
@Override
protected Configuration getConfiguration(String testName) throws IOException {
File testDir = tmpDir.newFolder(testName);
Configuration conf = new Configuration();
conf.set(TxConstants.Manager.CFG_TX_SNAPSHOT_LOCAL_DIR, testDir.getAbsolutePath());
conf.set(TxConstants.Persist.CFG_TX_SNAPHOT_CODEC_CLASSES, SnapshotCodecV4.class.getName());
return conf;
}
@Override
protected AbstractTransactionStateStorage getStorage(Configuration conf) {
return new LocalFileTransactionStateStorage(conf, new SnapshotCodecProvider(conf), new TxMetricsCollector());
}
// v2 TransactionEdit
@SuppressWarnings("deprecation")
private class TransactionEditV2 extends TransactionEdit {
public TransactionEditV2(long writePointer, long visibilityUpperBound, State state, long expirationDate,
Set<ChangeId> changes, long commitPointer, boolean canCommit, TransactionType type) {
super(writePointer, visibilityUpperBound, state, expirationDate, changes, commitPointer, canCommit, type,
null, 0L, 0L, null);
}
@Override
public void write(DataOutput out) throws IOException {
TransactionEditCodecs.encode(this, out, new TransactionEditCodecs.TransactionEditCodecV2());
}
}
// Note: this test cannot run in AbstractTransactionStateStorageTest, since SequenceFile throws exception saying
// TransactionEditV2 is not TransactionEdit. Since the code path this test is verifying is the same path between
// HDFS and Local Storage, having this only over here is fine.
@SuppressWarnings("deprecation")
@Test
public void testLongTxnBackwardsCompatibility() throws Exception {
Configuration conf = getConfiguration("testLongTxnBackwardsCompatibility");
// Use SnapshotCodec version 1
String latestSnapshotCodec = conf.get(TxConstants.Persist.CFG_TX_SNAPHOT_CODEC_CLASSES);
conf.set(TxConstants.Persist.CFG_TX_SNAPHOT_CODEC_CLASSES, DefaultSnapshotCodec.class.getName());
TransactionStateStorage storage = null;
try {
storage = getStorage(conf);
storage.startAndWait();
// Create transaction snapshot and transaction edits with version when long running txns had -1 expiration.
Collection<Long> invalid = Lists.newArrayList();
NavigableMap<Long, TransactionManager.InProgressTx> inProgress = Maps.newTreeMap();
long time1 = System.currentTimeMillis();
long wp1 = time1 * TxConstants.MAX_TX_PER_MS;
inProgress.put(wp1, new TransactionManager.InProgressTx(wp1 - 5, -1L));
long time2 = time1 + 100;
long wp2 = time2 * TxConstants.MAX_TX_PER_MS;
inProgress.put(wp2, new TransactionManager.InProgressTx(wp2 - 50, time2 + 1000));
Map<Long, Set<ChangeId>> committing = Maps.newHashMap();
Map<Long, Set<ChangeId>> committed = Maps.newHashMap();
TransactionSnapshot snapshot = new TransactionSnapshot(time2, 0, wp2, invalid,
inProgress, committing, committed);
long time3 = time1 + 200;
long wp3 = time3 * TxConstants.MAX_TX_PER_MS;
TransactionEdit edit1 = new TransactionEditV2(wp3, wp3 - 10, TransactionEdit.State.INPROGRESS, -1L,
null, 0L, false, null);
long time4 = time1 + 300;
long wp4 = time4 * TxConstants.MAX_TX_PER_MS;
TransactionEdit edit2 = new TransactionEditV2(wp4, wp4 - 10, TransactionEdit.State.INPROGRESS, time4 + 1000,
null, 0L, false, null);
// write snapshot and transaction edit
storage.writeSnapshot(snapshot);
TransactionLog log = storage.createLog(time2);
log.append(edit1);
log.append(edit2);
log.close();
// Start transaction manager
conf.set(TxConstants.Persist.CFG_TX_SNAPHOT_CODEC_CLASSES, latestSnapshotCodec);
long longTimeout = TimeUnit.SECONDS.toMillis(conf.getLong(TxConstants.Manager.CFG_TX_LONG_TIMEOUT,
TxConstants.Manager.DEFAULT_TX_LONG_TIMEOUT));
TransactionManager txm = new TransactionManager(conf, storage, new TxMetricsCollector());
txm.startAndWait();
try {
// Verify that the txns in old format were read correctly.
// There should be four in-progress transactions, and no invalid transactions
TransactionSnapshot snapshot1 = txm.getCurrentState();
Assert.assertEquals(ImmutableSortedSet.of(wp1, wp2, wp3, wp4), snapshot1.getInProgress().keySet());
verifyInProgress(snapshot1.getInProgress().get(wp1), TransactionType.LONG, time1 + longTimeout);
verifyInProgress(snapshot1.getInProgress().get(wp2), TransactionType.SHORT, time2 + 1000);
verifyInProgress(snapshot1.getInProgress().get(wp3), TransactionType.LONG, time3 + longTimeout);
verifyInProgress(snapshot1.getInProgress().get(wp4), TransactionType.SHORT, time4 + 1000);
Assert.assertEquals(0, snapshot1.getInvalid().size());
} finally {
txm.stopAndWait();
}
} finally {
if (storage != null) {
storage.stopAndWait();
}
}
}
// Note: this test cannot run in AbstractTransactionStateStorageTest, since SequenceFile throws exception saying
// TransactionEditV2 is not TransactionEdit. Since the code path this test is verifying is the same path between
// HDFS and Local Storage, having this only over here is fine.
@SuppressWarnings("deprecation")
@Test
public void testAbortEditBackwardsCompatibility() throws Exception {
Configuration conf = getConfiguration("testAbortEditBackwardsCompatibility");
TransactionStateStorage storage = null;
try {
storage = getStorage(conf);
storage.startAndWait();
// Create edits for transaction type addition to abort
long time1 = System.currentTimeMillis();
long wp1 = time1 * TxConstants.MAX_TX_PER_MS;
TransactionEdit edit1 = new TransactionEditV2(wp1, wp1 - 10, TransactionEdit.State.INPROGRESS, -1L,
null, 0L, false, null);
TransactionEdit edit2 = new TransactionEditV2(wp1, 0L, TransactionEdit.State.ABORTED, 0L,
null, 0L, false, null);
long time2 = time1 + 400;
long wp2 = time2 * TxConstants.MAX_TX_PER_MS;
TransactionEdit edit3 = new TransactionEditV2(wp2, wp2 - 10, TransactionEdit.State.INPROGRESS, time2 + 10000,
null, 0L, false, null);
TransactionEdit edit4 = new TransactionEditV2(wp2, 0L, TransactionEdit.State.INVALID, 0L, null, 0L, false, null);
// Simulate case where we cannot determine txn state during abort
TransactionEdit edit5 = new TransactionEditV2(wp2, 0L, TransactionEdit.State.ABORTED, 0L, null, 0L, false, null);
// write snapshot and transaction edit
TransactionLog log = storage.createLog(time1);
log.append(edit1);
log.append(edit2);
log.append(edit3);
log.append(edit4);
log.append(edit5);
log.close();
// Start transaction manager
TransactionManager txm = new TransactionManager(conf, storage, new TxMetricsCollector());
txm.startAndWait();
try {
// Verify that the txns in old format were read correctly.
// Both transactions should be in invalid state
TransactionSnapshot snapshot1 = txm.getCurrentState();
Assert.assertEquals(ImmutableList.of(wp1, wp2), snapshot1.getInvalid());
Assert.assertEquals(0, snapshot1.getInProgress().size());
Assert.assertEquals(0, snapshot1.getCommittedChangeSets().size());
Assert.assertEquals(0, snapshot1.getCommittingChangeSets().size());
} finally {
txm.stopAndWait();
}
} finally {
if (storage != null) {
storage.stopAndWait();
}
}
}
private void verifyInProgress(TransactionManager.InProgressTx inProgressTx, TransactionType type,
long expiration) throws Exception {
Assert.assertEquals(type, inProgressTx.getType());
Assert.assertTrue(inProgressTx.getExpiration() == expiration);
}
}