/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Feb 21, 2008
*/
package com.bigdata.resources;
import java.io.IOException;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import com.bigdata.btree.AbstractBTreeTestCase;
import com.bigdata.btree.BTree;
import com.bigdata.btree.IIndex;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.btree.IndexSegment;
import com.bigdata.btree.IndexSegmentStore;
import com.bigdata.btree.keys.TestKeyBuilder;
import com.bigdata.btree.proc.IIndexProcedure;
import com.bigdata.btree.proc.BatchInsert.BatchInsertConstructor;
import com.bigdata.journal.AbstractJournal;
import com.bigdata.journal.ITx;
import com.bigdata.journal.IndexProcedureTask;
import com.bigdata.journal.RegisterIndexTask;
import com.bigdata.mdi.IResourceMetadata;
import com.bigdata.mdi.IndexPartitionCause;
import com.bigdata.mdi.LocalPartitionMetadata;
import com.bigdata.mdi.MetadataIndex;
import com.bigdata.rawstore.SimpleMemoryRawStore;
/**
* Basic test of compacting merge for an index partition on overflow.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestMergeTask extends AbstractResourceManagerTestCase {
/**
*
*/
public TestMergeTask() {
super();
}
/**
* @param arg0
*/
public TestMergeTask(String arg0) {
super(arg0);
}
/**
* Test generates an {@link IndexSegment} from a (typically historical)
* fused view of an index partition. The resulting {@link IndexSegment} is a
* complete replacement for the historical view but does not possess any
* deleted index entries. Typically the {@link IndexSegment} will be used to
* replace the current index partition definition such that the resources
* that were the inputs to the view from which the {@link IndexSegment} was
* built are no longer required to read on that view. This change needs to
* be recorded in the {@link MetadataIndex} before clients will being
* reading from the new view using the new {@link IndexSegment}.
*
* @throws IOException
* @throws ExecutionException
* @throws InterruptedException
*
* @todo test more complex merges.
*/
public void test_mergeWithOverflow() throws IOException,
InterruptedException, ExecutionException {
/*
* Register the index.
*/
final String name = "testIndex";
final UUID indexUUID = UUID.randomUUID();
final IndexMetadata indexMetadata = new IndexMetadata(name, indexUUID);
{
// must support delete markers
indexMetadata.setDeleteMarkers(true);
// must be an index partition.
indexMetadata.setPartitionMetadata(new LocalPartitionMetadata(
0, // partitionId.
-1, // not a move.
new byte[] {}, // leftSeparator
null, // rightSeparator
new IResourceMetadata[] {//
resourceManager.getLiveJournal().getResourceMetadata(), //
}, //
IndexPartitionCause.register(resourceManager)
// ,"" // history
));
// submit task to register the index and wait for it to complete.
concurrencyManager.submit(
new RegisterIndexTask(concurrencyManager, name,
indexMetadata)).get();
}
/*
* Populate the index with some data.
*/
final BTree groundTruth = BTree.create(new SimpleMemoryRawStore(),
new IndexMetadata(indexUUID));
{
final int nentries = 10;
final byte[][] keys = new byte[nentries][];
final byte[][] vals = new byte[nentries][];
final Random r = new Random();
for (int i = 0; i < nentries; i++) {
keys[i] = TestKeyBuilder.asSortKey(i);
vals[i] = new byte[4];
r.nextBytes(vals[i]);
groundTruth.insert(keys[i], vals[i]);
}
final IIndexProcedure proc = BatchInsertConstructor.RETURN_NO_VALUES
.newInstance(indexMetadata, 0/* fromIndex */,
nentries/*toIndex*/, keys, vals);
// submit the task and wait for it to complete.
concurrencyManager.submit(
new IndexProcedureTask(concurrencyManager, ITx.UNISOLATED,
name, proc)).get();
}
/*
* Force overflow causing an empty btree to be created for that index on
* a new journal and the view definition in the new btree to be updated.
*/
// createTime of the old journal.
final long createTime0 = resourceManager.getLiveJournal()
.getRootBlockView().getCreateTime();
// uuid of the old journal.
final UUID uuid0 = resourceManager.getLiveJournal().getRootBlockView()
.getUUID();
// force overflow onto a new journal.
final OverflowMetadata overflowMetadata = resourceManager
.doSynchronousOverflow();
// nothing should have been copied to the new journal.
assertEquals(0, overflowMetadata
.getActionCount(OverflowActionEnum.Copy));
// lookup the old journal again using its createTime.
final AbstractJournal oldJournal = resourceManager
.getJournal(createTime0);
assertEquals("uuid", uuid0, oldJournal.getRootBlockView().getUUID());
assertNotSame("closeTime", 0L, oldJournal.getRootBlockView()
.getCloseTime());
// run merge task.
final BuildResult result;
{
/*
* Note: The task start time is a historical read on the final
* committed state of the old journal. This means that the generated
* index segment will have a createTime EQ to the lastCommitTime on
* the old journal. This also means that it will have been generated
* from a fused view of all data as of the final commit state of the
* old journal.
*/
// final OverflowMetadata omd = new OverflowMetadata(resourceManager);
final ViewMetadata vmd = overflowMetadata.getViewMetadata(name);
// task to run.
final CompactingMergeTask task = new CompactingMergeTask(vmd);
try {
// overflow must be disallowed as a task pre-condition.
resourceManager.overflowAllowed.compareAndSet(true, false);
/*
* Submit task and await result (metadata describing the new
* index segment).
*/
result = concurrencyManager.submit(task).get();
} finally {
// re-enable overflow processing.
resourceManager.overflowAllowed.set(true);
}
final IResourceMetadata segmentMetadata = result.segmentMetadata;
if (log.isInfoEnabled())
log.info(segmentMetadata.toString());
// verify index segment can be opened.
resourceManager.openStore(segmentMetadata.getUUID());
// Note: this assertion only works if we store the file path vs its basename.
// assertTrue(new File(segmentMetadata.getFile()).exists());
// verify createTime == lastCommitTime on the old journal.
assertEquals("createTime", oldJournal.getRootBlockView()
.getLastCommitTime(), segmentMetadata.getCreateTime());
// verify segment has all data in the groundTruth btree.
{
final IndexSegmentStore segStore = (IndexSegmentStore) resourceManager
.openStore(segmentMetadata.getUUID());
final IndexSegment seg = segStore.loadIndexSegment();
AbstractBTreeTestCase.assertSameBTree(groundTruth, seg);
}
}
/*
* verify same data from ground truth and the new view (using btree
* helper classes for this).
*/
{
final IIndex actual = resourceManager
.getIndex(name, ITx.UNISOLATED);
AbstractBTreeTestCase.assertSameBTree(groundTruth, actual);
}
}
}