/*
* Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
*/
package org.opendaylight.controller.cluster.raft;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload;
+import org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload;
-import static org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockReplicatedLogEntry;
/**
-*
+* Unit tests for AbstractReplicatedLogImplTest.
*/
public class AbstractReplicatedLogImplTest {
@Before
public void setUp() {
replicatedLogImpl = new MockAbstractReplicatedLogImpl();
+ // create a set of initial entries in the in-memory log
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(0, 1, new MockPayload("A")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(1, 1, new MockPayload("B")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(2, 1, new MockPayload("C")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(3, 2, new MockPayload("D")));
+
}
- @After
- public void tearDown() {
- replicatedLogImpl.journal.clear();
- replicatedLogImpl.setSnapshotIndex(-1);
- replicatedLogImpl.setSnapshotTerm(-1);
- replicatedLogImpl = null;
+ @Test
+ public void testEmptyLog() {
+ replicatedLogImpl = new MockAbstractReplicatedLogImpl();
+
+ assertEquals("size", 0, replicatedLogImpl.size());
+ assertEquals("dataSize", 0, replicatedLogImpl.dataSize());
+ assertEquals("getSnapshotIndex", -1, replicatedLogImpl.getSnapshotIndex());
+ assertEquals("getSnapshotTerm", -1, replicatedLogImpl.getSnapshotTerm());
+ assertEquals("lastIndex", -1, replicatedLogImpl.lastIndex());
+ assertEquals("lastTerm", -1, replicatedLogImpl.lastTerm());
+ assertEquals("isPresent", false, replicatedLogImpl.isPresent(0));
+ assertEquals("isInSnapshot", false, replicatedLogImpl.isInSnapshot(0));
+ assertNull("get(0)", replicatedLogImpl.get(0));
+ assertNull("last", replicatedLogImpl.last());
+
+ List<ReplicatedLogEntry> list = replicatedLogImpl.getFrom(0, 1, ReplicatedLog.NO_MAX_SIZE);
+ assertEquals("getFrom size", 0, list.size());
+
+ assertEquals("removeFrom", -1, replicatedLogImpl.removeFrom(1));
+
+ replicatedLogImpl.setSnapshotIndex(2);
+ replicatedLogImpl.setSnapshotTerm(1);
+
+ assertEquals("getSnapshotIndex", 2, replicatedLogImpl.getSnapshotIndex());
+ assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm());
+ assertEquals("lastIndex", 2, replicatedLogImpl.lastIndex());
+ assertEquals("lastTerm", 1, replicatedLogImpl.lastTerm());
}
@Test
public void testIndexOperations() {
- // create a set of initial entries in the in-memory log
- replicatedLogImpl.append(new MockReplicatedLogEntry(1, 0, new MockPayload("A")));
- replicatedLogImpl.append(new MockReplicatedLogEntry(1, 1, new MockPayload("B")));
- replicatedLogImpl.append(new MockReplicatedLogEntry(1, 2, new MockPayload("C")));
- replicatedLogImpl.append(new MockReplicatedLogEntry(2, 3, new MockPayload("D")));
// check if the values returned are correct, with snapshotIndex = -1
assertEquals("B", replicatedLogImpl.get(1).getData().toString());
// now create a snapshot of 3 entries, with 1 unapplied entry left in the log
// It removes the entries which have made it to snapshot
// and updates the snapshot index and term
- Map state = takeSnapshot(3);
+ takeSnapshot(3);
// check the values after the snapshot.
// each index value passed in the test is the logical index (log entry index)
assertTrue(replicatedLogImpl.isInSnapshot(2));
// append few more entries
- replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("E")));
- replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("F")));
- replicatedLogImpl.append(new MockReplicatedLogEntry(3, 6, new MockPayload("G")));
- replicatedLogImpl.append(new MockReplicatedLogEntry(3, 7, new MockPayload("H")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(4, 2, new MockPayload("E")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(5, 2, new MockPayload("F")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(6, 3, new MockPayload("G")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(7, 3, new MockPayload("H")));
// check their values as well
assertEquals(5, replicatedLogImpl.size());
assertEquals(2, replicatedLogImpl.getFrom(6).size());
// take a second snapshot with 5 entries with 0 unapplied entries left in the log
- state = takeSnapshot(5);
+ takeSnapshot(5);
assertEquals(0, replicatedLogImpl.size());
assertNull(replicatedLogImpl.last());
}
+ @Test
+ public void testGetFromWithMax() {
+ List<ReplicatedLogEntry> from = replicatedLogImpl.getFrom(0, 1, ReplicatedLog.NO_MAX_SIZE);
+ assertEquals(1, from.size());
+ assertEquals("A", from.get(0).getData().toString());
+
+ from = replicatedLogImpl.getFrom(0, 20, ReplicatedLog.NO_MAX_SIZE);
+ assertEquals(4, from.size());
+ assertEquals("A", from.get(0).getData().toString());
+ assertEquals("D", from.get(3).getData().toString());
+
+ from = replicatedLogImpl.getFrom(1, 2, ReplicatedLog.NO_MAX_SIZE);
+ assertEquals(2, from.size());
+ assertEquals("B", from.get(0).getData().toString());
+ assertEquals("C", from.get(1).getData().toString());
+
+ from = replicatedLogImpl.getFrom(1, 3, 2);
+ assertEquals(2, from.size());
+ assertEquals("B", from.get(0).getData().toString());
+ assertEquals("C", from.get(1).getData().toString());
+
+ from = replicatedLogImpl.getFrom(1, 3, 3);
+ assertEquals(3, from.size());
+ assertEquals("B", from.get(0).getData().toString());
+ assertEquals("C", from.get(1).getData().toString());
+ assertEquals("D", from.get(2).getData().toString());
+
+ from = replicatedLogImpl.getFrom(1, 2, 3);
+ assertEquals(2, from.size());
+ assertEquals("B", from.get(0).getData().toString());
+ assertEquals("C", from.get(1).getData().toString());
+
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(4, 2, new MockPayload("12345")));
+ from = replicatedLogImpl.getFrom(4, 2, 2);
+ assertEquals(1, from.size());
+ assertEquals("12345", from.get(0).getData().toString());
+ }
+
+ @Test
+ public void testSnapshotPreCommit() {
+ //add 4 more entries
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(4, 2, new MockPayload("E")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(5, 2, new MockPayload("F")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(6, 3, new MockPayload("G")));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(7, 3, new MockPayload("H")));
+
+ //sending negative values should not cause any changes
+ replicatedLogImpl.snapshotPreCommit(-1, -1);
+ assertEquals(8, replicatedLogImpl.size());
+ assertEquals(-1, replicatedLogImpl.getSnapshotIndex());
+ assertEquals(-1, replicatedLogImpl.getSnapshotTerm());
+
+ replicatedLogImpl.snapshotPreCommit(4, 2);
+ assertEquals(3, replicatedLogImpl.size());
+ assertEquals(4, replicatedLogImpl.getSnapshotIndex());
+ assertEquals(2, replicatedLogImpl.getSnapshotTerm());
+
+ replicatedLogImpl.snapshotPreCommit(6, 3);
+ assertEquals(1, replicatedLogImpl.size());
+ assertEquals(6, replicatedLogImpl.getSnapshotIndex());
+ assertEquals(3, replicatedLogImpl.getSnapshotTerm());
+
+ replicatedLogImpl.snapshotPreCommit(7, 3);
+ assertEquals(0, replicatedLogImpl.size());
+ assertEquals(7, replicatedLogImpl.getSnapshotIndex());
+ assertEquals(3, replicatedLogImpl.getSnapshotTerm());
+
+ //running it again on an empty list should not throw exception
+ replicatedLogImpl.snapshotPreCommit(7, 3);
+ assertEquals(0, replicatedLogImpl.size());
+ assertEquals(7, replicatedLogImpl.getSnapshotIndex());
+ assertEquals(3, replicatedLogImpl.getSnapshotTerm());
+ }
+
+ @Test
+ public void testSnapshotCommit() {
+
+ replicatedLogImpl.snapshotPreCommit(1, 1);
+
+ replicatedLogImpl.snapshotCommit();
+
+ assertEquals("size", 2, replicatedLogImpl.size());
+ assertEquals("dataSize", 2, replicatedLogImpl.dataSize());
+ assertEquals("getSnapshotIndex", 1, replicatedLogImpl.getSnapshotIndex());
+ assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm());
+ assertEquals("lastIndex", 3, replicatedLogImpl.lastIndex());
+ assertEquals("lastTerm", 2, replicatedLogImpl.lastTerm());
+
+ assertNull("get(0)", replicatedLogImpl.get(0));
+ assertNull("get(1)", replicatedLogImpl.get(1));
+ assertNotNull("get(2)", replicatedLogImpl.get(2));
+ assertNotNull("get(3)", replicatedLogImpl.get(3));
+ }
+
+ @Test
+ public void testSnapshotRollback() {
+
+ replicatedLogImpl.snapshotPreCommit(1, 1);
+
+ assertEquals("size", 2, replicatedLogImpl.size());
+ assertEquals("getSnapshotIndex", 1, replicatedLogImpl.getSnapshotIndex());
+ assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm());
+
+ replicatedLogImpl.snapshotRollback();
+
+ assertEquals("size", 4, replicatedLogImpl.size());
+ assertEquals("dataSize", 4, replicatedLogImpl.dataSize());
+ assertEquals("getSnapshotIndex", -1, replicatedLogImpl.getSnapshotIndex());
+ assertEquals("getSnapshotTerm", -1, replicatedLogImpl.getSnapshotTerm());
+ assertNotNull("get(0)", replicatedLogImpl.get(0));
+ assertNotNull("get(3)", replicatedLogImpl.get(3));
+ }
+
+ @Test
+ public void testIsPresent() {
+ assertTrue(replicatedLogImpl.isPresent(0));
+ assertTrue(replicatedLogImpl.isPresent(1));
+ assertTrue(replicatedLogImpl.isPresent(2));
+ assertTrue(replicatedLogImpl.isPresent(3));
+
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(4, 2, new MockPayload("D")));
+ replicatedLogImpl.snapshotPreCommit(3, 2); //snapshot on 3
+ replicatedLogImpl.snapshotCommit();
+
+ assertFalse(replicatedLogImpl.isPresent(0));
+ assertFalse(replicatedLogImpl.isPresent(1));
+ assertFalse(replicatedLogImpl.isPresent(2));
+ assertFalse(replicatedLogImpl.isPresent(3));
+ assertTrue(replicatedLogImpl.isPresent(4));
+
+ replicatedLogImpl.snapshotPreCommit(4, 2); //snapshot on 4
+ replicatedLogImpl.snapshotCommit();
+ assertFalse(replicatedLogImpl.isPresent(4));
+
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(5, 2, new MockPayload("D")));
+ assertTrue(replicatedLogImpl.isPresent(5));
+ }
+
+ @Test
+ public void testRemoveFrom() {
+
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(4, 2, new MockPayload("E", 2)));
+ replicatedLogImpl.append(new SimpleReplicatedLogEntry(5, 2, new MockPayload("F", 3)));
+
+ assertEquals("dataSize", 9, replicatedLogImpl.dataSize());
+
+ long adjusted = replicatedLogImpl.removeFrom(4);
+ assertEquals("removeFrom - adjusted", 4, adjusted);
+ assertEquals("size", 4, replicatedLogImpl.size());
+ assertEquals("dataSize", 4, replicatedLogImpl.dataSize());
+
+ takeSnapshot(1);
+
+ adjusted = replicatedLogImpl.removeFrom(2);
+ assertEquals("removeFrom - adjusted", 1, adjusted);
+ assertEquals("size", 1, replicatedLogImpl.size());
+ assertEquals("dataSize", 1, replicatedLogImpl.dataSize());
+
+ assertEquals("removeFrom - adjusted", -1, replicatedLogImpl.removeFrom(0));
+ assertEquals("removeFrom - adjusted", -1, replicatedLogImpl.removeFrom(100));
+ }
+
// create a snapshot for test
- public Map takeSnapshot(int numEntries) {
- Map map = new HashMap(numEntries);
- List<ReplicatedLogEntry> entries = replicatedLogImpl.getEntriesTill(numEntries);
- for (ReplicatedLogEntry entry : entries) {
+ public Map<Long, String> takeSnapshot(final int numEntries) {
+ Map<Long, String> map = new HashMap<>(numEntries);
+
+ long lastIndex = 0;
+ long lastTerm = 0;
+ for (int i = 0; i < numEntries; i++) {
+ ReplicatedLogEntry entry = replicatedLogImpl.getAtPhysicalIndex(i);
map.put(entry.getIndex(), entry.getData().toString());
+ lastIndex = entry.getIndex();
+ lastTerm = entry.getTerm();
}
- int term = (int) replicatedLogImpl.lastTerm();
- int lastIndex = (int) entries.get(entries.size() - 1).getIndex();
- entries.clear();
- replicatedLogImpl.setSnapshotTerm(term);
- replicatedLogImpl.setSnapshotIndex(lastIndex);
+ replicatedLogImpl.snapshotPreCommit(lastIndex, lastTerm);
+ replicatedLogImpl.snapshotCommit();
return map;
}
- class MockAbstractReplicatedLogImpl extends AbstractReplicatedLogImpl {
- @Override
- public void appendAndPersist(ReplicatedLogEntry replicatedLogEntry) {
- }
+ static class MockAbstractReplicatedLogImpl extends AbstractReplicatedLogImpl {
@Override
- public void removeFromAndPersist(long index) {
+ public boolean removeFromAndPersist(final long index) {
+ return true;
}
- public void setSnapshotIndex(long snapshotIndex) {
- this.snapshotIndex = snapshotIndex;
+ @Override
+ public boolean appendAndPersist(final ReplicatedLogEntry replicatedLogEntry,
+ final Consumer<ReplicatedLogEntry> callback, final boolean doAsync) {
+ if (callback != null) {
+ callback.accept(replicatedLogEntry);
+ }
+ return true;
}
- public void setSnapshotTerm(long snapshotTerm) {
- this.snapshotTerm = snapshotTerm;
+ @Override
+ public void captureSnapshotIfReady(final ReplicatedLogEntry replicatedLogEntry) {
+ // No-op
}
- public List<ReplicatedLogEntry> getEntriesTill(int index) {
- return journal.subList(0, index);
+ @Override
+ public boolean shouldCaptureSnapshot(final long logIndex) {
+ return false;
}
}
}