summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2013-10-25 01:36:27 +0000
committerJesse Morgan <jesse@jesterpm.net>2013-10-25 01:36:27 +0000
commitc4be3cddd66744623a229c3a05b125b2a5cdd234 (patch)
tree5f79d7b2e5f3b56b881f8378d04cbd8fabd92692
parent2607738aabab4d97a9528b22b6f5f7a3cea09e02 (diff)
Fixing ReplicatedRepository so that transactions may be entered when the master
is unavailable as long as no changes are made to replicated storables.
-rw-r--r--src/test/java/com/amazon/carbonado/TestUtilities.java12
-rw-r--r--src/test/java/com/amazon/carbonado/repo/replicated/TestBrokenMaster.java219
2 files changed, 229 insertions, 2 deletions
diff --git a/src/test/java/com/amazon/carbonado/TestUtilities.java b/src/test/java/com/amazon/carbonado/TestUtilities.java
index d7be174..cbee9c1 100644
--- a/src/test/java/com/amazon/carbonado/TestUtilities.java
+++ b/src/test/java/com/amazon/carbonado/TestUtilities.java
@@ -38,7 +38,7 @@ public class TestUtilities {
public static final String FILE_PATH_KEY = "filepath";
// Keep memory usage low to prevent spurious out-of-memory errors while running tests.
- private static final int DEFAULT_CAPACITY = 100000;
+ public static final int DEFAULT_CAPACITY = 100000;
private static final Random sRandom = new Random();
@@ -122,12 +122,20 @@ public class TestUtilities {
int capacity,
boolean isMaster)
{
+ return newTempRepositoryBuilder(name, capacity, isMaster, true);
+ }
+
+ public static RepositoryBuilder newTempRepositoryBuilder(String name,
+ int capacity,
+ boolean isMaster,
+ boolean inMemory)
+ {
BDBRepositoryBuilder builder = new BDBRepositoryBuilder();
builder.setProduct("JE");
builder.setName(name);
builder.setTransactionNoSync(true);
builder.setCacheSize(capacity);
- builder.setLogInMemory(true);
+ builder.setLogInMemory(inMemory);
builder.setMaster(isMaster);
builder.setEnvironmentHome(makeTestDirectoryString(name));
// Makes it easier to get a thread dump during a deadlock.
diff --git a/src/test/java/com/amazon/carbonado/repo/replicated/TestBrokenMaster.java b/src/test/java/com/amazon/carbonado/repo/replicated/TestBrokenMaster.java
new file mode 100644
index 0000000..3238a90
--- /dev/null
+++ b/src/test/java/com/amazon/carbonado/repo/replicated/TestBrokenMaster.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2013 Amazon.com Inc. All Rights Reserved.
+ * AMAZON.COM CONFIDENTIAL
+ */
+
+package com.amazon.carbonado.repo.replicated;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.amazon.carbonado.ConfigurationException;
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryBuilder;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.TestUtilities;
+import com.amazon.carbonado.TriggerFactory;
+
+import com.amazon.carbonado.layout.TestLayout;
+import com.amazon.carbonado.stored.StorableTestMinimal;
+
+import org.cojen.classfile.TypeDesc;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Test ReplicatedRepository when the master repository builder throws an
+ * Exception at build time.
+ *
+ * @author Jesse Morgan (morganjm)
+ */
+public class TestBrokenMaster extends TestCase {
+ private static final String STORABLE_NAME = "test.TestStorable";
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTestSuite(TestBrokenMaster.class);
+ return suite;
+ }
+
+ public TestBrokenMaster(String name) {
+ super(name);
+ }
+
+ private RepositoryBuilder mReplicaBuilder;
+ private BrokenRepositoryBuilder mMasterBuilder;
+ private Repository mRepository;
+
+ private Class<? extends StorableTestMinimal> mTestStorableClazz;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mReplicaBuilder = TestUtilities.newTempRepositoryBuilder("rr-replica", TestUtilities.DEFAULT_CAPACITY, false, false);
+ mMasterBuilder = new BrokenRepositoryBuilder(TestUtilities.newTempRepositoryBuilder("rr-writer", TestUtilities.DEFAULT_CAPACITY, true, false));
+
+ // Prepare the Storable class
+ mTestStorableClazz = TestLayout.defineStorable(STORABLE_NAME, 0, TypeDesc.INT);
+
+ // Populate data
+ mRepository = buildRepository(false);
+ Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(mTestStorableClazz);
+ StorableTestMinimal s = storage.prepare();
+ s.setId(1);
+ s.insert();
+ mRepository.close();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ if (mRepository != null) {
+ mRepository.close();
+ mRepository = null;
+ }
+ }
+
+ private Repository buildRepository(boolean broken) throws RepositoryException {
+ if (broken) {
+ mMasterBuilder.setFailures(1);
+ }
+ ReplicatedRepositoryBuilder builder = new ReplicatedRepositoryBuilder();
+ builder.setMaster(true);
+ builder.setReplicaRepositoryBuilder(mReplicaBuilder);
+ builder.setMasterRepositoryBuilder(mMasterBuilder);
+ return builder.build();
+ }
+
+ /**
+ * Verify that reads work and inserts fail on a broken ReplicatedRepository.
+ */
+ public void testLoadAndInsert() throws Exception {
+ mRepository = buildRepository(true);
+ Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(mTestStorableClazz);
+
+ // Test loading existing an Storable with a broken master.
+ // This should succeed.
+ StorableTestMinimal s = storage.prepare();
+ s.setId(1);
+ s.load();
+
+ // Test inserting a Storable with broken master.
+ // This should fail.
+ try {
+ s = storage.prepare();
+ s.setId(2);
+ s.insert();
+
+ fail("Expected exception.");
+ } catch (PersistException e) {
+ assertEquals("Current transaction is read-only.", e.getMessage());
+ }
+ }
+
+ /**
+ * Verify that Storables can't change on a broken ReplicatedRepository.
+ */
+ public void testStorableChanged() throws Exception {
+ // Change the Storable definition
+ mTestStorableClazz = TestLayout.defineStorable(STORABLE_NAME, 1, TypeDesc.INT);
+
+ // Open the repository.
+ mRepository = buildRepository(true);
+
+ // Test loading existing an Storable with a broken master.
+ // This should fail because the layout has changed and the new layout
+ // can not be persisted.
+ try {
+ Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(mTestStorableClazz);
+ StorableTestMinimal s = storage.prepare();
+ s.setId(1);
+ s.load();
+ fail("Expected exception");
+ } catch (PersistException e) {
+ assertEquals("com.amazon.carbonado.PersistException: Current transaction is read-only.", e.getMessage());
+ }
+ }
+
+ /**
+ * This class wraps a RepositoryBuilder and fails to build the first n times.
+ *
+ * This is used to imitate a JDBC timeout at startup.
+ */
+ class BrokenRepositoryBuilder implements RepositoryBuilder {
+ private RepositoryBuilder mWrappedBuilder;
+ private int mFailuresRemaining;
+
+ public BrokenRepositoryBuilder(RepositoryBuilder builder) {
+ mWrappedBuilder = builder;
+ }
+
+ public void setFailures(int i) {
+ mFailuresRemaining = i;
+ }
+
+ @Override
+ public Repository build() throws ConfigurationException, RepositoryException {
+ return mWrappedBuilder.build();
+ }
+
+ @Override
+ public Repository build(AtomicReference<Repository> rootReference)
+ throws ConfigurationException, RepositoryException {
+
+ if (mFailuresRemaining > 0) {
+ mFailuresRemaining--;
+ throw new FetchException("Failed Build Enabled");
+ }
+
+ return mWrappedBuilder.build(rootReference);
+ }
+
+ @Override
+ public String getName() {
+ return mWrappedBuilder.getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ mWrappedBuilder.setName(name);
+ }
+
+ @Override
+ public boolean isMaster() {
+ return mWrappedBuilder.isMaster();
+ }
+
+ @Override
+ public void setMaster(boolean b) {
+ mWrappedBuilder.setMaster(b);
+ }
+
+ @Override
+ public boolean addTriggerFactory(TriggerFactory factory) {
+ return mWrappedBuilder.addTriggerFactory(factory);
+ }
+
+ @Override
+ public boolean removeTriggerFactory(TriggerFactory factory) {
+ return mWrappedBuilder.removeTriggerFactory(factory);
+ }
+
+ @Override
+ public Iterable<TriggerFactory> getTriggerFactories() {
+ return mWrappedBuilder.getTriggerFactories();
+ }
+ }
+}