/* $HeadURL:: $
* $Id$
*
* Copyright (c) 2009-2010 DuraSpace
* http://duraspace.org
*
* In collaboration with Topaz Inc.
* http://www.topazproject.org
*
* 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 org.akubraproject.qsc;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import org.apache.commons.io.FileUtils;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import org.akubraproject.AkubraException;
import org.akubraproject.Blob;
import org.akubraproject.BlobStore;
import org.akubraproject.BlobStoreConnection;
import org.akubraproject.mem.MemBlobStore;
import org.akubraproject.tck.TCKTestSuite;
import org.akubraproject.txn.derby.TransactionalStore;
/**
* TCK test suite for {@link QuiescingBlobStore}.
*
* @author Chris Wilper;
*/
public class QuiescingBlobStoreTCKTest {
@Factory
public Object[] createTests() throws Exception {
URI storeId1 = URI.create("urn:qsc-tck-test:42");
URI storeId2 = URI.create("urn:qsc-tck-test:43");
BlobStore nonTxnStore = new MemBlobStore(URI.create("urn:store:1"));
BlobStore txnStore =
createTxnStore("qsc-txn-text-1", new MemBlobStore(URI.create("urn:store:2")));
return new Object[] {
new QuiescingBlobStoreTestSuite(new QuiescingBlobStore(storeId1, nonTxnStore), storeId1,
false, true),
new QuiescingBlobStoreTestSuite(new QuiescingBlobStore(storeId2, txnStore), storeId2,
true, false),
};
}
private BlobStore createTxnStore(String name, BlobStore backingStore) throws IOException {
File base = new File(System.getProperty("basedir"), "target");
File dbDir = new File(base, name);
FileUtils.deleteDirectory(dbDir);
dbDir.getParentFile().mkdirs();
System.setProperty("derby.stream.error.file", new File(base, "derby.log").toString());
BlobStore store = new TransactionalStore(URI.create("urn:" + name), backingStore, dbDir.getPath());
return store;
}
public static class QuiescingBlobStoreTestSuite extends TCKTestSuite {
public QuiescingBlobStoreTestSuite(BlobStore store, URI storeId, boolean isTransactional,
boolean supportsIdGen) {
super(store, storeId, isTransactional, supportsIdGen);
}
@Override
protected URI[] getAliases(URI uri) {
// for underlying mem store, all uris are distinct
return new URI[] { uri };
}
@Override
protected URI getInvalidId() {
// for underlying mem store, all uris are valid
return null;
}
/**
* Request to go quiescent and non-quiescent (even when already in those
* states) should be supported.
*/
@Test(groups={ "store" }, dependsOnGroups={ "init" })
public void testSetQuiescent() throws Exception {
// basic set (non-)quiescent
assertTrue(((QuiescingBlobStore) store).setQuiescent(true));
assertTrue(((QuiescingBlobStore) store).setQuiescent(true));
assertTrue(((QuiescingBlobStore) store).setQuiescent(false));
assertTrue(((QuiescingBlobStore) store).setQuiescent(false));
// in quiescent state: read-only should proceed, write should block
final URI id1 = createId("blobSetQuiescent1");
final URI id2 = createId("blobSetQuiescent2");
createBlob(id1, "bar", true);
runTests(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
final Blob b1 = getBlob(con, id1, "bar");
final Blob b2 = getBlob(con, id2, false);
assertTrue(((QuiescingBlobStore) store).setQuiescent(true));
try {
doWithTimeout(new ERunnable() {
@Override
public void erun() throws Exception {
assertTrue(b1.exists());
}
}, 100, true);
doWithTimeout(new ERunnable() {
@Override
public void erun() throws Exception {
assertFalse(b2.exists());
}
}, 100, true);
doWithTimeout(new ERunnable() {
@Override
public void erun() throws Exception {
assertEquals(getBody(b1), "bar");
}
}, 100, true);
if (isOutputSupp) {
doWithTimeout(new ERunnable() {
@Override
public void erun() throws Exception {
b1.openOutputStream(-1, true);
}
}, 100, false);
}
if (isDeleteSupp) {
doWithTimeout(new ERunnable() {
@Override
public void erun() throws Exception {
b1.delete();
}
}, 100, false);
}
if (isMoveToSupp) {
doWithTimeout(new ERunnable() {
@Override
public void erun() throws Exception {
b1.moveTo(b2.getId(), null);
}
}, 100, false);
}
} finally {
assertTrue(((QuiescingBlobStore) store).setQuiescent(false));
}
}
}, true);
// going quiescent should wait for in-progress write operations to complete
if (isOutputSupp) {
final boolean[] cv = new boolean[] { false };
final boolean[] failed = new boolean[] { false };
final Thread t = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
waitFor(cv, true, 0);
try {
assertTrue(((QuiescingBlobStore) store).setQuiescent(true));
} finally {
assertTrue(((QuiescingBlobStore) store).setQuiescent(false));
}
}
}, failed);
runTests(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
final Blob b1 = getBlob(con, id1, "bar");
OutputStream os = b1.openOutputStream(-1, true);
TCKTestSuite.notify(cv, true);
t.join(100);
assertTrue(t.isAlive());
os.close();
t.join(100);
if (isTransactional) {
assertTrue(t.isAlive());
} else {
assertFalse(t.isAlive());
assertFalse(failed[0]);
}
}
}, true);
if (isTransactional) {
t.join(100);
assertFalse(t.isAlive());
assertFalse(failed[0]);
}
assertTrue(((QuiescingBlobStore) store).setQuiescent(false));
}
// clean up
assertTrue(((QuiescingBlobStore) store).setQuiescent(false));
deleteBlob(id1, "", true);
assertNoBlobs(getPrefixFor("blobSetQuiescent"));
}
private void doWithTimeout(final ERunnable test, final long timeout, final boolean expSucc)
throws Exception {
boolean[] failed = new boolean[] { false };
Thread t = doInThread(new Runnable() {
public void run() {
try {
test.erun();
if (!expSucc)
fail("Did not get expected exception");
} catch (IOException ioe) {
if (expSucc || ioe instanceof AkubraException)
throw new RuntimeException(ioe);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}, failed);
t.join(timeout);
if (expSucc) {
assertFalse(t.isAlive());
} else {
assertTrue(t.isAlive());
t.interrupt();
t.join(timeout);
assertFalse(t.isAlive(), "operation failed to return after interrupt");
}
assertFalse(failed[0]);
}
}
}