SharedLog runtime proto

ONOS-1806, ONOS-1807, ONOS-1808

Change-Id: Ic86cb7bdc6b04a81180ab43afa01c1aea4ac18c7
diff --git a/src/test/java/net/onrc/onos/core/util/distributed/sharedlog/SeqNumTest.java b/src/test/java/net/onrc/onos/core/util/distributed/sharedlog/SeqNumTest.java
new file mode 100644
index 0000000..9ae7897
--- /dev/null
+++ b/src/test/java/net/onrc/onos/core/util/distributed/sharedlog/SeqNumTest.java
@@ -0,0 +1,157 @@
+package net.onrc.onos.core.util.distributed.sharedlog;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+
+import org.junit.Test;
+
+import com.google.common.primitives.UnsignedLongs;
+
+/**
+ * Basic {@link SeqNum} class tests.
+ */
+public class SeqNumTest {
+
+    /**
+     * Tests {@link SeqNum#next()} points to next sequence number,
+     * excluding reserved INITIAL value,
+     * and correctly wraps around ULONG_MAX.
+     */
+    @Test
+    public void testNext() {
+        final SeqNum one = SeqNum.INITIAL.next();
+        assertEquals(1L, one.longValue());
+
+        // succ
+        final SeqNum two = one.next();
+        assertEquals(2L, two.longValue());
+
+        // succ wraps around skipping INITIAL
+        final SeqNum max = SeqNum.valueOf(UnsignedLongs.MAX_VALUE);
+        assertEquals(one, max.next());
+    }
+
+    /**
+     * Tests {@link SeqNum#prev()} points to previous sequence number,
+     * excluding reserved INITIAL value,
+     * and correctly wraps around ULONG_MAX.
+     */
+    @Test
+    public void testPrev() {
+
+        // prev
+        final SeqNum two = SeqNum.valueOf(2);
+        final SeqNum one = two.prev();
+        assertEquals(1L, one.longValue());
+
+        final SeqNum max = SeqNum.INITIAL.prev();
+        assertEquals(UnsignedLongs.MAX_VALUE, max.longValue());
+
+        // prev wraps around skipping INITIAL
+        assertEquals(max, one.prev());
+    }
+
+    /**
+     * Tests that SeqNum equals and hashCode.
+     */
+    @Test
+    public void testEqualsObject() {
+        final SeqNum s = SeqNum.valueOf(42L);
+
+        assertTrue(s.equals(s));
+        assertEquals(s.hashCode(), s.hashCode());
+
+        assertTrue(s.equals(s.next().prev()));
+        assertEquals(s.hashCode(), s.next().prev().hashCode());
+
+        assertFalse(s.equals(s.next()));
+        assertFalse(s.equals(s.prev()));
+        assertFalse(s.equals(null));
+        assertFalse(s.equals(Long.valueOf(42L)));
+    }
+
+    /**
+     * Tests that SeqNum is converted to String as unsigned decimal.
+     */
+    @Test
+    public void testToString() {
+        assertEquals("0", SeqNum.INITIAL.toString());
+        assertEquals("1", SeqNum.valueOf(1).toString());
+        // toString format is unsigned decimal string
+        assertEquals("9223372036854775808",
+                     SeqNum.valueOf(Long.MAX_VALUE + 1).toString());
+    }
+
+    /**
+     * Tests that comparison works treating long value as point in a ring.
+     */
+    @Test
+    public void testCompareTo() {
+        final SeqNum zero = SeqNum.INITIAL;
+        final SeqNum one = SeqNum.valueOf(1);
+        final SeqNum two = SeqNum.valueOf(2);
+        final SeqNum oneAlt = zero.next();
+        final SeqNum negOne = one.prev();
+
+        // 0 < 1
+        assertThat(zero.compareTo(one), lessThan(0));
+        // 1 > 0
+        assertThat(one.compareTo(zero), greaterThan(0));
+
+        // 0 == 0
+        assertThat(zero.compareTo(zero), equalTo(0));
+        // 1 == 1
+        assertThat(oneAlt.compareTo(one), equalTo(0));
+        assertThat(one.compareTo(oneAlt), equalTo(0));
+
+        // 2 > 1
+        assertThat(two.compareTo(one), greaterThan(0));
+        // 1 < 2
+        assertThat(one.compareTo(two), lessThan(0));
+
+        // (-1) < 1
+        assertThat(negOne.compareTo(one), lessThan(0));
+        // 1 > (-1)
+        assertThat(one.compareTo(negOne), greaterThan(0));
+
+        // (-1) > (-3)
+        assertThat(negOne.compareTo(negOne.prev().prev()), greaterThan(0));
+        // (-3) < (-1)
+        assertThat(negOne.prev().prev().compareTo(negOne), lessThan(0));
+
+        // (-1) > 0 [0 is always the smallest element]
+        assertThat(negOne.compareTo(zero), greaterThan(0));
+        // 0 < (-1) [0 is always the smallest element]
+        assertThat(zero.compareTo(negOne), lessThan(0));
+
+        /// comparison using shorter arc
+
+        // 0 < SLONG_MAX+1(=HALF) [clockwise arc used]
+        assertThat(zero.compareTo(zero.step(Long.MAX_VALUE).next()), lessThan(0));
+        assertThat(zero.step(Long.MAX_VALUE).next().compareTo(zero), greaterThan(0));
+        /// 0 is always compared clock wise (never wraps)
+        // 0 < SLONG_MAX+1(=HALF)+1 [clockwise arc used]
+        assertThat(zero.compareTo(zero.step(Long.MAX_VALUE).next().next()), lessThan(0));
+        assertThat(zero.step(Long.MAX_VALUE).next().next().compareTo(zero), greaterThan(0));
+
+        // 1 < 1+SLONG_MAX(=HALF-1) [clockwise arc used]
+        assertThat(one.compareTo(one.step(Long.MAX_VALUE)), lessThan(0));
+        assertThat(one.step(Long.MAX_VALUE).compareTo(one), greaterThan(0));
+        // 1 < 1+SLONG_MAX+1(=HALF) [SAME, counter-clockwise arc used]
+        assertThat(one.compareTo(one.step(Long.MAX_VALUE).next()), greaterThan(0));
+        assertThat(one.step(Long.MAX_VALUE).next().compareTo(one), lessThan(0));
+        // 1 < 1+SLONG_MAX+2(=HALF+1) [counter-clockwise arc used]
+        assertThat(one.compareTo(one.step(Long.MAX_VALUE).next().next()), greaterThan(0));
+        assertThat(one.step(Long.MAX_VALUE).next().next().compareTo(one), lessThan(0));
+
+        // (-1) < (-1)+SLONG_MAX(=HALF-1) [clockwise arc used]
+        assertThat(negOne.compareTo(negOne.step(Long.MAX_VALUE)), lessThan(0));
+        assertThat(negOne.step(Long.MAX_VALUE).compareTo(negOne), greaterThan(0));
+        // (-1) < (-1)+SLONG_MAX+1(=HALF) [SAME, clockwise arc used]
+        assertThat(negOne.compareTo(negOne.step(Long.MAX_VALUE).next()), lessThan(0));
+        assertThat(negOne.step(Long.MAX_VALUE).next().compareTo(negOne), greaterThan(0));
+        // (-1) > (-1)+SLONG_MAX+2(=HALF+1) [counter-clockwise arc used]
+        assertThat(negOne.compareTo(negOne.step(Long.MAX_VALUE).next().next()), greaterThan(0));
+        assertThat(negOne.step(Long.MAX_VALUE).next().next().compareTo(negOne), lessThan(0));
+    }
+}
diff --git a/src/test/java/net/onrc/onos/core/util/distributed/sharedlog/example/LogAtomicLongTest.java b/src/test/java/net/onrc/onos/core/util/distributed/sharedlog/example/LogAtomicLongTest.java
new file mode 100644
index 0000000..91fd2be
--- /dev/null
+++ b/src/test/java/net/onrc/onos/core/util/distributed/sharedlog/example/LogAtomicLongTest.java
@@ -0,0 +1,132 @@
+package net.onrc.onos.core.util.distributed.sharedlog.example;
+
+import static org.junit.Assert.*;
+
+import java.util.UUID;
+
+import net.onrc.onos.core.datastore.hazelcast.HZClient;
+import net.onrc.onos.core.util.IntegrationTest;
+import net.onrc.onos.core.util.TestUtils;
+import net.onrc.onos.core.util.distributed.sharedlog.hazelcast.HazelcastRuntime;
+import net.onrc.onos.core.util.distributed.sharedlog.hazelcast.HazelcastSequencerRuntime;
+import net.onrc.onos.core.util.distributed.sharedlog.runtime.LogBasedRuntime;
+import net.onrc.onos.core.util.distributed.sharedlog.runtime.SequencerRuntime;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import com.hazelcast.core.HazelcastInstance;
+
+/**
+ * Unit test to run LogAtomicLong example.
+ */
+public class LogAtomicLongTest {
+
+    static {
+        // configuration to quickly fall back to instance mode for faster test run
+        System.setProperty("net.onrc.onos.core.datastore.hazelcast.client.attemptLimit", "0");
+    }
+
+
+    private LogAtomicLong along;
+
+    /**
+     * Create LogAtomicLong instance.
+     */
+    @Before
+    public void setUp() {
+        final HZClient cl = HZClient.getClient();
+        HazelcastInstance hz = TestUtils.callMethod(cl, "getHZInstance", null);
+
+        SequencerRuntime sequencerRuntime = new HazelcastSequencerRuntime(hz);
+        LogBasedRuntime runtime = new HazelcastRuntime(hz, sequencerRuntime);
+
+        String counterName = UUID.randomUUID().toString();
+        along = new LogAtomicLong(runtime, counterName);
+    }
+
+    /**
+     * Test unconditional write.
+     */
+    @Test
+    public void testSet() {
+        along.set(42);
+        assertEquals(42, along.get());
+        along.set(0);
+        assertEquals(0, along.get());
+    }
+
+    /**
+     * Test conditional write.
+     */
+    @Test
+    public void testCompareAndSet() {
+        along.set(42);
+        assertEquals(42, along.get());
+
+        along.compareAndSet(42, 43);
+        assertEquals(43, along.get());
+
+        // should remain unchanged if expectation not met
+        along.compareAndSet(42, 45);
+        assertEquals(43, along.get());
+    }
+
+    /**
+     * Confirm initial value is 0.
+     */
+    @Test
+    public void testGet() {
+        assertEquals(0, along.get());
+    }
+
+    /**
+     * Confirm another instance with same ID observes the same value.
+     */
+    @Test
+    public void testOtherInstance() {
+        along.set(42);
+        assertEquals(42, along.get());
+
+        final HZClient cl = HZClient.getClient();
+        HazelcastInstance hz = TestUtils.callMethod(cl, "getHZInstance", null);
+
+        SequencerRuntime sequencerRuntime = new HazelcastSequencerRuntime(hz);
+        LogBasedRuntime runtime = new HazelcastRuntime(hz, sequencerRuntime);
+
+        LogAtomicLong anotherInstance = new LogAtomicLong(runtime,
+                                                along.getObjectID());
+        assertEquals(42, anotherInstance.get());
+    }
+
+    /**
+     * Confirm another instance with same ID initializes using snapshot.
+     */
+    @Category(IntegrationTest.class)
+    @Test
+    public void testOtherInstanceFromSnapshot() {
+        along.set(42);
+        assertEquals(42, along.get());
+
+        final HZClient cl = HZClient.getClient();
+        HazelcastInstance hz = TestUtils.callMethod(cl, "getHZInstance", null);
+
+        SequencerRuntime sequencerRuntime = new HazelcastSequencerRuntime(hz);
+        LogBasedRuntime runtime = new HazelcastRuntime(hz, sequencerRuntime);
+
+        // FIXME SNAPSHOT_INTERVAL should be customized to smaller values
+        // write multiple times to trigger snapshot
+        for (int i = 0; i < HazelcastRuntime.SNAPSHOT_INTERVAL + 2; ++i) {
+            along.set(i);
+        }
+        along.set(99);
+        Thread.yield();
+
+        // this instance might start from latest snap shot then replay
+        LogAtomicLong anotherInstance2 = new LogAtomicLong(runtime,
+                along.getObjectID());
+        assertEquals(99, anotherInstance2.get());
+
+    }
+}