/**
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 Oct 15, 2007
*/
package com.bigdata.journal;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import com.bigdata.btree.IIndex;
import com.bigdata.btree.ILocalBTreeView;
import com.bigdata.btree.IndexMetadata;
/**
* Test suite for hierarchical locking of indices based on namespace prefixes.
* This test suite was introduced as part of the support for group commit for
* the RDF database layer in the non-scale-out modes. In order to be able to run
* RDF operations using job-based concurrency, we need to predeclare the
* namespace (rather than the individual indices) and then isolate all indices
* spanned by that namespace.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*
* @see <a href="- http://sourceforge.net/apps/trac/bigdata/ticket/566" >
* Concurrent unisolated operations against multiple KBs </a>
*
* FIXME GROUP COMMIT (hierarchical locking): We need to test each
* different kind of index access here (unisolated, isolated by a
* read/write tx, and isolated by a read-only tx, and read-historical (no
* isolation)).
*/
public class TestHierarchicalLockingTasks extends ProxyTestCase<Journal> {
/**
*
*/
public TestHierarchicalLockingTasks() {
super();
}
/**
* @param name
*/
public TestHierarchicalLockingTasks(final String name) {
super(name);
}
/**
* Test creates several named indices some of which have a shared namespace
* prefix and verifies that a lock declared for the namespace prefix permits
* that task to access any index in that namespace. The test also verifies
* that an index that does not share the namespace prefix may not be
* accessed by the task.
*
* @throws InterruptedException
* @throws ExecutionException
*/
public void test_hierarchicalLocking_001() throws InterruptedException,
ExecutionException {
// namespace for declared locks.
final String[] namespace = new String[] { "foo" };
// indices that we create, write on, and verify.
final String[] allowed_indices = new String[] { "foo.bar", "foo.baz" };
// indices that are outside of the declared namespace. we verify that we
// can not access these indices.
final String[] disallowed_indices = new String[] { "goo", "goo.bar" };
final Journal journal = getStore();
try {
/*
* Create indices that we should not be able to see when holding
* just the lock for the namespace.
*/
for (int i = 0; i < disallowed_indices.length; i++) {
final String name = disallowed_indices[i];
journal.submit(new RegisterIndexTask(journal
.getConcurrencyManager(), name, new IndexMetadata(name,
UUID.randomUUID()))).get();
}
/*
* Create indices that we should be able to see when holding the
* lock for the namespace.
*/
for (int i = 0; i < allowed_indices.length; i++) {
final String name = allowed_indices[i];
journal.submit(new RegisterIndexTask(journal
.getConcurrencyManager(), name, new IndexMetadata(name,
UUID.randomUUID()))).get();
}
/*
* Submit task holding the lock for the namespace and verify that we
* can see the indices that are spanned by the namespace, but not
* those that are not spanned by the namespace.
*/
journal.submit(
new AbstractTask<Void>(journal.getConcurrencyManager(),
ITx.UNISOLATED, namespace) {
@Override
protected Void doTask() throws Exception {
// Verify access to the indices in that namespace.
for (String name : allowed_indices) {
assertNotNull(name, getIndex(name));
}
/*
* Verify no access to the indices outside of that
* namespace.
*/
for (String name : disallowed_indices) {
try {
// Attempt to access index.
getIndex(name);
fail("Expecting: "
+ IllegalStateException.class
+ " for " + name);
} catch (IllegalStateException ex) {
// log and ignore expected exception.
if (log.isInfoEnabled())
log.info("Ignoring expected exception");
}
}
// Done.
return null;
}
}).get();
} finally {
journal.destroy();
}
}
/**
* Unit test for hierarchical locking verifies that we can declared a
* namespace for a task which then creates multiple indices spanned by that
* namespace.
*
* @throws InterruptedException
* @throws ExecutionException
*/
public void test_hierarchicalLocking_create_destroy()
throws InterruptedException, ExecutionException {
// namespace for declared locks.
final String[] namespace = new String[] { "foo" };
// indices that we create, write on, and verify.
final String[] allowed_indices = new String[] { "foo.bar", "foo.baz" };
final Journal journal = getStore();
try {
final UUID[] uuids = journal.submit(
new AbstractTask<UUID[]>(journal.getConcurrencyManager(),
ITx.UNISOLATED, namespace) {
@Override
protected UUID[] doTask() throws Exception {
final UUID[] indexUUIDs = new UUID[allowed_indices.length];
/*
* Create indices that we should be able to see when
* holding the lock for the namespace.
*/
for (int i = 0; i < allowed_indices.length; i++) {
final String name = allowed_indices[i];
IIndex ndx = getJournal().getIndex(name);
if (ndx != null) {
final UUID indexUUID = ndx.getIndexMetadata().getIndexUUID();
if (log.isInfoEnabled())
log.info("Index exists: name=" + name + ", indexUUID=" + indexUUID);
indexUUIDs[i] = indexUUID;
}
// register the index.
ndx = getJournal().registerIndex(
name,
new IndexMetadata(name, UUID
.randomUUID()));
final UUID indexUUID = ndx.getIndexMetadata().getIndexUUID();
if (log.isInfoEnabled())
log.info("Registered index: name=" + name + ", class="
+ ndx.getClass() + ", indexUUID=" + indexUUID);
indexUUIDs[i] = indexUUID;
}
// Done
return indexUUIDs;
}
}).get();
// should be non-null.
assertNotNull(uuids);
// Should be non-null.
for (UUID uuid : uuids) {
assertNotNull(uuid);
}
/*
* Verify access to the newly created indices.
*/
journal.submit(
new AbstractTask<Void>(journal.getConcurrencyManager(),
ITx.UNISOLATED, namespace) {
@Override
protected Void doTask() throws Exception {
/*
* Verify access to the newly created indices.
*/
for (int i = 0; i < allowed_indices.length; i++) {
final String name = allowed_indices[i];
final ILocalBTreeView ndx = getIndex(name);
assertNotNull(ndx);
assertEquals(0L, ndx.rangeCount());
// add a key.
ndx.insert(new byte[] {},
new byte[] { (byte) i });
}
// Done
return null;
}
}).get();
/*
* Destroy the newly created indices.
*/
journal.submit(
new AbstractTask<Void>(journal.getConcurrencyManager(),
ITx.UNISOLATED, namespace) {
@Override
protected Void doTask() throws Exception {
/*
* Create indices that we should be able to see when
* holding the lock for the namespace.
*/
for (int i = 0; i < allowed_indices.length; i++) {
final String name = allowed_indices[i];
getJournal().dropIndex(name);
}
// Done
return null;
}
}).get();
/*
* Verify that the indices can no longer be accessed.
*/
journal.submit(
new AbstractTask<Void>(journal.getConcurrencyManager(),
ITx.UNISOLATED, namespace) {
@Override
protected Void doTask() throws Exception {
/*
* Verify no access to the dropped indices.
*/
for (int i = 0; i < allowed_indices.length; i++) {
final String name = allowed_indices[i];
try {
// Attempt to access index.
getIndex(name);
fail("Expecting: "
+ IllegalStateException.class
+ " for " + name);
} catch (IllegalStateException ex) {
// log and ignore expected exception.
if (log.isInfoEnabled())
log.info("Ignoring expected exception");
}
}
// Done
return null;
}
}).get();
} finally {
journal.destroy();
}
}
}