package edu.berkeley.thebes.hat.server.dependencies; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import edu.berkeley.thebes.common.data.DataItem; import edu.berkeley.thebes.common.data.Version; import edu.berkeley.thebes.common.persistence.IPersistenceEngine; import edu.berkeley.thebes.common.thrift.ThriftDataItem; import edu.berkeley.thebes.hat.server.antientropy.clustering.AntiEntropyServiceRouter; import java.nio.ByteBuffer; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.thrift.TException; public class DependencyResolverTest extends TestCase { private static final short CLIENT_ID = 0; private AtomicLong logicalClock; private DependencyResolver resolver; private MockPersistenceEngine persistenceEngine; private MockRouter router; @Override public void setUp() { persistenceEngine = new MockPersistenceEngine(); router = new MockRouter(); resolver = new DependencyResolver(router, persistenceEngine); logicalClock = new AtomicLong(0); } public void testBasic() throws TException { Version xact1 = getTransactionId(); router.expect(xact1); resolver.addPendingWrite("hello", makeDataItem(xact1, "World!", "depKey1", "depKey2")); router.expect(null); assertPending("hello", "World!", xact1); resolver.ackTransactionPending(xact1); assertPending("hello", "World!", xact1); resolver.ackTransactionPending(xact1); assertGood("hello", "World!", xact1); } public void testWaitForAllSelf() throws TException { Version xact1 = getTransactionId(); router.expect(xact1); resolver.addPendingWrite("hello", makeDataItem(xact1, "World!", "hello", "depKey0", "depKey1", "depKey2")); try { router.expect(null); fail(); } catch (AssertionFailedError e) { /* ok */ } assertPending("hello", "World!", xact1); resolver.ackTransactionPending(xact1); assertPending("hello", "World!", xact1); resolver.addPendingWrite("depKey0", makeDataItem(xact1, "Worldz!", "hello", "depKey0", "depKey1", "depKey2")); router.expect(null); assertPending("hello", "World!", xact1); assertPending("depKey0", "Worldz!", xact1); resolver.ackTransactionPending(xact1); assertPending("hello", "World!", xact1); assertPending("depKey0", "Worldz!", xact1); resolver.ackTransactionPending(xact1); assertGood("hello", "World!", xact1); assertGood("depKey0", "Worldz!", xact1); } public void testPrematureAck() throws TException { Version xact1 = getTransactionId(); resolver.ackTransactionPending(xact1); router.expect(xact1); resolver.addPendingWrite("hello", makeDataItem(xact1, "World!", "depKey1", "depKey2")); assertPending("hello", "World!", xact1); resolver.ackTransactionPending(xact1); assertGood("hello", "World!", xact1); } public void testPrematureAckAll() throws TException { Version xact1 = getTransactionId(); resolver.ackTransactionPending(xact1); resolver.ackTransactionPending(xact1); router.expect(xact1); resolver.addPendingWrite("hello", makeDataItem(xact1, "World!", "depKey1", "depKey2")); assertGood("hello", "World!", xact1); } public void testUnrelated() throws TException { Version xact1 = getTransactionId(); Version xact2 = getTransactionId(); router.expect(xact1); resolver.addPendingWrite("hello", makeDataItem(xact1, "World!", "depKey1", "depKey2")); router.expect(xact2); resolver.addPendingWrite("other", makeDataItem(xact2, "value!", "depKey3", "depKey4")); assertPending("hello", "World!", xact1); assertPending("other", "value!", xact2); resolver.ackTransactionPending(xact1); assertPending("hello", "World!", xact1); assertPending("other", "value!", xact2); resolver.ackTransactionPending(xact2); assertPending("hello", "World!", xact1); assertPending("other", "value!", xact2); resolver.ackTransactionPending(xact2); assertPending("hello", "World!", xact1); assertGood("other", "value!", xact2); resolver.ackTransactionPending(xact1); assertGood("hello", "World!", xact1); assertGood("other", "value!", xact2); } public void testSameKey() throws TException { Version xact1 = getTransactionId(); Version xact2 = getTransactionId(); router.expect(xact1); resolver.addPendingWrite("hello", makeDataItem(xact1, "World!", "depKey1", "depKey2")); router.expect(xact2); resolver.addPendingWrite("hello", makeDataItem(xact2, "value!", "depKey3", "depKey4")); assertPending("hello", "World!", xact1); assertPending("hello", "value!", xact2); resolver.ackTransactionPending(xact1); assertPending("hello", "World!", xact1); assertPending("hello", "value!", xact2); resolver.ackTransactionPending(xact2); assertPending("hello", "World!", xact1); assertPending("hello", "value!", xact2); resolver.ackTransactionPending(xact2); assertPending("hello", "World!", xact1); assertGood("hello", "value!", xact2); resolver.ackTransactionPending(xact1); assertGood("hello", "value!", xact1); assertGood("hello", "value!", xact2); } public void testSameKeyPrematureAcks() throws TException { Version xact1 = getTransactionId(); Version xact2 = getTransactionId(); resolver.ackTransactionPending(xact1); resolver.ackTransactionPending(xact2); router.expect(xact1); resolver.addPendingWrite("hello", makeDataItem(xact1, "World!", "depKey1", "depKey2")); router.expect(xact2); resolver.addPendingWrite("hello", makeDataItem(xact2, "value!", "depKey3", "depKey4")); assertPending("hello", "World!", xact1); assertPending("hello", "value!", xact2); resolver.ackTransactionPending(xact2); assertPending("hello", "World!", xact1); assertGood("hello", "value!", xact2); resolver.ackTransactionPending(xact1); assertGood("hello", "value!", xact1); assertGood("hello", "value!", xact2); } private void assertPending(String key, String value, Version version) throws TException { if (resolver.retrievePendingItem(key, version) != null) { assertEquals(ByteBuffer.wrap(value.getBytes()), resolver.retrievePendingItem(key, version).getData()); } else { fail("Not in pending!"); } if (persistenceEngine.get(key) != null) { assertFalse(ByteBuffer.wrap(value.getBytes()).equals(persistenceEngine.get(key).getData())); } } private void assertGood(String key, String value, Version version) { //assertNull( resolver.retrievePendingItem(key, version)); assertEquals(ByteBuffer.wrap(value.getBytes()), persistenceEngine.get(key).getData()); } private DataItem makeDataItem(Version xact, String value, String ... transactionKeys) { DataItem di = new DataItem(ByteBuffer.wrap(value.getBytes()), xact); di.setTransactionKeys(Lists.newArrayList(transactionKeys)); return di; } private Version getTransactionId() { return new Version(CLIENT_ID, logicalClock.incrementAndGet(), System.currentTimeMillis()); } private static class MockRouter extends AntiEntropyServiceRouter { private Version expectAnnounceID; public void expect(Version key) { assertNull(expectAnnounceID); expectAnnounceID = key; } @Override public void sendWriteToSiblings(String key, ThriftDataItem value) { } @Override public void announceTransactionReady(Version transactionID, Set<Integer> servers) { assertEquals(transactionID, expectAnnounceID); expectAnnounceID = null; } } private static class MockPersistenceEngine implements IPersistenceEngine { private final Map<String, DataItem> data = Maps.newHashMap(); @Override public void force_put(String key, DataItem value) { throw new UnsupportedOperationException(); } @Override public void put_if_newer(String key, DataItem value) { if (!data.containsKey(key)) { data.put(key, value); } else if (data.get(key).getVersion().compareTo(value.getVersion()) <= 0) { data.put(key, value); } } @Override public DataItem get(String key) { return data.get(key); } @Override public void open() {} @Override public void close() {} @Override public void delete(String key) {} } }