From c4be3cddd66744623a229c3a05b125b2a5cdd234 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Fri, 25 Oct 2013 01:36:27 +0000 Subject: Fixing ReplicatedRepository so that transactions may be entered when the master is unavailable as long as no changes are made to replicated storables. --- .../java/com/amazon/carbonado/TestUtilities.java | 12 +- .../repo/replicated/TestBrokenMaster.java | 219 +++++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/amazon/carbonado/repo/replicated/TestBrokenMaster.java (limited to 'src') 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(); @@ -121,13 +121,21 @@ public class TestUtilities { public static RepositoryBuilder newTempRepositoryBuilder(String name, 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 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 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 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 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 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 getTriggerFactories() { + return mWrappedBuilder.getTriggerFactories(); + } + } +} -- cgit v1.2.3