Bug 2787: Batch AppendEntries to speed up follower sync
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / AbstractReplicatedLogImplTest.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  * Copyright (c) 2015 Brocade Communications Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9 package org.opendaylight.controller.cluster.raft;
10
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertFalse;
13 import static org.junit.Assert.assertNull;
14 import static org.junit.Assert.assertTrue;
15 import akka.japi.Procedure;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import org.junit.Assert;
20 import org.junit.Before;
21 import org.junit.Test;
22 import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload;
23 import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockReplicatedLogEntry;
24
25 /**
26 *
27 */
28 public class AbstractReplicatedLogImplTest {
29
30     private MockAbstractReplicatedLogImpl replicatedLogImpl;
31
32     @Before
33     public void setUp() {
34         replicatedLogImpl = new MockAbstractReplicatedLogImpl();
35         // create a set of initial entries in the in-memory log
36         replicatedLogImpl.append(new MockReplicatedLogEntry(1, 0, new MockPayload("A")));
37         replicatedLogImpl.append(new MockReplicatedLogEntry(1, 1, new MockPayload("B")));
38         replicatedLogImpl.append(new MockReplicatedLogEntry(1, 2, new MockPayload("C")));
39         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 3, new MockPayload("D")));
40
41     }
42
43     @Test
44     public void testEmptyLog() {
45         replicatedLogImpl = new MockAbstractReplicatedLogImpl();
46
47         assertEquals("size", 0, replicatedLogImpl.size());
48         assertEquals("dataSize", 0, replicatedLogImpl.dataSize());
49         assertEquals("getSnapshotIndex", -1, replicatedLogImpl.getSnapshotIndex());
50         assertEquals("getSnapshotTerm", -1, replicatedLogImpl.getSnapshotTerm());
51         assertEquals("lastIndex", -1, replicatedLogImpl.lastIndex());
52         assertEquals("lastTerm", -1, replicatedLogImpl.lastTerm());
53         assertEquals("isPresent", false, replicatedLogImpl.isPresent(0));
54         assertEquals("isInSnapshot", false, replicatedLogImpl.isInSnapshot(0));
55         Assert.assertNull("get(0)", replicatedLogImpl.get(0));
56         Assert.assertNull("last", replicatedLogImpl.last());
57
58         List<ReplicatedLogEntry> list = replicatedLogImpl.getFrom(0, 1, ReplicatedLog.NO_MAX_SIZE);
59         assertEquals("getFrom size", 0, list.size());
60
61         assertEquals("removeFrom", -1, replicatedLogImpl.removeFrom(1));
62
63         replicatedLogImpl.setSnapshotIndex(2);
64         replicatedLogImpl.setSnapshotTerm(1);
65
66         assertEquals("getSnapshotIndex", 2, replicatedLogImpl.getSnapshotIndex());
67         assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm());
68         assertEquals("lastIndex", 2, replicatedLogImpl.lastIndex());
69         assertEquals("lastTerm", 1, replicatedLogImpl.lastTerm());
70     }
71
72     @Test
73     public void testIndexOperations() {
74
75         // check if the values returned are correct, with snapshotIndex = -1
76         assertEquals("B", replicatedLogImpl.get(1).getData().toString());
77         assertEquals("D", replicatedLogImpl.last().getData().toString());
78         assertEquals(3, replicatedLogImpl.lastIndex());
79         assertEquals(2, replicatedLogImpl.lastTerm());
80         assertEquals(2, replicatedLogImpl.getFrom(2).size());
81         assertEquals(4, replicatedLogImpl.size());
82         assertTrue(replicatedLogImpl.isPresent(2));
83         assertFalse(replicatedLogImpl.isPresent(4));
84         assertFalse(replicatedLogImpl.isInSnapshot(2));
85
86         // now create a snapshot of 3 entries, with 1 unapplied entry left in the log
87         // It removes the entries which have made it to snapshot
88         // and updates the snapshot index and term
89         takeSnapshot(3);
90
91         // check the values after the snapshot.
92         // each index value passed in the test is the logical index (log entry index)
93         // which gets mapped to the list's physical index
94         assertEquals("D", replicatedLogImpl.get(3).getData().toString());
95         assertEquals("D", replicatedLogImpl.last().getData().toString());
96         assertNull(replicatedLogImpl.get(1));
97         assertEquals(3, replicatedLogImpl.lastIndex());
98         assertEquals(2, replicatedLogImpl.lastTerm());
99         assertEquals(0, replicatedLogImpl.getFrom(2).size());
100         assertEquals(1, replicatedLogImpl.size());
101         assertFalse(replicatedLogImpl.isPresent(2));
102         assertTrue(replicatedLogImpl.isPresent(3));
103         assertFalse(replicatedLogImpl.isPresent(4));
104         assertTrue(replicatedLogImpl.isInSnapshot(2));
105
106         // append few more entries
107         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("E")));
108         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("F")));
109         replicatedLogImpl.append(new MockReplicatedLogEntry(3, 6, new MockPayload("G")));
110         replicatedLogImpl.append(new MockReplicatedLogEntry(3, 7, new MockPayload("H")));
111
112         // check their values as well
113         assertEquals(5, replicatedLogImpl.size());
114         assertEquals("D", replicatedLogImpl.get(3).getData().toString());
115         assertEquals("E", replicatedLogImpl.get(4).getData().toString());
116         assertEquals("H", replicatedLogImpl.last().getData().toString());
117         assertEquals(3, replicatedLogImpl.lastTerm());
118         assertEquals(7, replicatedLogImpl.lastIndex());
119         assertTrue(replicatedLogImpl.isPresent(7));
120         assertFalse(replicatedLogImpl.isInSnapshot(7));
121         assertEquals(1, replicatedLogImpl.getFrom(7).size());
122         assertEquals(2, replicatedLogImpl.getFrom(6).size());
123
124         // take a second snapshot with 5 entries with 0 unapplied entries left in the log
125         takeSnapshot(5);
126
127         assertEquals(0, replicatedLogImpl.size());
128         assertNull(replicatedLogImpl.last());
129         assertNull(replicatedLogImpl.get(7));
130         assertNull(replicatedLogImpl.get(1));
131         assertFalse(replicatedLogImpl.isPresent(7));
132         assertTrue(replicatedLogImpl.isInSnapshot(7));
133         assertEquals(0, replicatedLogImpl.getFrom(7).size());
134         assertEquals(0, replicatedLogImpl.getFrom(6).size());
135
136     }
137
138     @Test
139     public void testGetFromWithMax() {
140         List<ReplicatedLogEntry> from = replicatedLogImpl.getFrom(0, 1, ReplicatedLog.NO_MAX_SIZE);
141         Assert.assertEquals(1, from.size());
142         Assert.assertEquals("A", from.get(0).getData().toString());
143
144         from = replicatedLogImpl.getFrom(0, 20, ReplicatedLog.NO_MAX_SIZE);
145         Assert.assertEquals(4, from.size());
146         Assert.assertEquals("A", from.get(0).getData().toString());
147         Assert.assertEquals("D", from.get(3).getData().toString());
148
149         from = replicatedLogImpl.getFrom(1, 2, ReplicatedLog.NO_MAX_SIZE);
150         Assert.assertEquals(2, from.size());
151         Assert.assertEquals("B", from.get(0).getData().toString());
152         Assert.assertEquals("C", from.get(1).getData().toString());
153
154         from = replicatedLogImpl.getFrom(1, 3, 2);
155         Assert.assertEquals(2, from.size());
156         Assert.assertEquals("B", from.get(0).getData().toString());
157         Assert.assertEquals("C", from.get(1).getData().toString());
158
159         from = replicatedLogImpl.getFrom(1, 3, 3);
160         Assert.assertEquals(3, from.size());
161         Assert.assertEquals("B", from.get(0).getData().toString());
162         Assert.assertEquals("C", from.get(1).getData().toString());
163         Assert.assertEquals("D", from.get(2).getData().toString());
164
165         from = replicatedLogImpl.getFrom(1, 2, 3);
166         Assert.assertEquals(2, from.size());
167         Assert.assertEquals("B", from.get(0).getData().toString());
168         Assert.assertEquals("C", from.get(1).getData().toString());
169
170         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("12345")));
171         from = replicatedLogImpl.getFrom(4, 2, 2);
172         Assert.assertEquals(1, from.size());
173         Assert.assertEquals("12345", from.get(0).getData().toString());
174     }
175
176     @Test
177     public void testSnapshotPreCommit() {
178         //add 4 more entries
179         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("E")));
180         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("F")));
181         replicatedLogImpl.append(new MockReplicatedLogEntry(3, 6, new MockPayload("G")));
182         replicatedLogImpl.append(new MockReplicatedLogEntry(3, 7, new MockPayload("H")));
183
184         //sending negative values should not cause any changes
185         replicatedLogImpl.snapshotPreCommit(-1, -1);
186         assertEquals(8, replicatedLogImpl.size());
187         assertEquals(-1, replicatedLogImpl.getSnapshotIndex());
188         assertEquals(-1, replicatedLogImpl.getSnapshotTerm());
189
190         replicatedLogImpl.snapshotPreCommit(4, 2);
191         assertEquals(3, replicatedLogImpl.size());
192         assertEquals(4, replicatedLogImpl.getSnapshotIndex());
193         assertEquals(2, replicatedLogImpl.getSnapshotTerm());
194
195         replicatedLogImpl.snapshotPreCommit(6, 3);
196         assertEquals(1, replicatedLogImpl.size());
197         assertEquals(6, replicatedLogImpl.getSnapshotIndex());
198         assertEquals(3, replicatedLogImpl.getSnapshotTerm());
199
200         replicatedLogImpl.snapshotPreCommit(7, 3);
201         assertEquals(0, replicatedLogImpl.size());
202         assertEquals(7, replicatedLogImpl.getSnapshotIndex());
203         assertEquals(3, replicatedLogImpl.getSnapshotTerm());
204
205         //running it again on an empty list should not throw exception
206         replicatedLogImpl.snapshotPreCommit(7, 3);
207         assertEquals(0, replicatedLogImpl.size());
208         assertEquals(7, replicatedLogImpl.getSnapshotIndex());
209         assertEquals(3, replicatedLogImpl.getSnapshotTerm());
210     }
211
212     @Test
213     public void testSnapshotCommit() {
214
215         replicatedLogImpl.snapshotPreCommit(1, 1);
216
217         replicatedLogImpl.snapshotCommit();
218
219         assertEquals("size", 2, replicatedLogImpl.size());
220         assertEquals("dataSize", 2, replicatedLogImpl.dataSize());
221         assertEquals("getSnapshotIndex", 1, replicatedLogImpl.getSnapshotIndex());
222         assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm());
223         assertEquals("lastIndex", 3, replicatedLogImpl.lastIndex());
224         assertEquals("lastTerm", 2, replicatedLogImpl.lastTerm());
225
226         Assert.assertNull("get(0)", replicatedLogImpl.get(0));
227         Assert.assertNull("get(1)", replicatedLogImpl.get(1));
228         Assert.assertNotNull("get(2)", replicatedLogImpl.get(2));
229         Assert.assertNotNull("get(3)", replicatedLogImpl.get(3));
230     }
231
232     @Test
233     public void testSnapshotRollback() {
234
235         replicatedLogImpl.snapshotPreCommit(1, 1);
236
237         assertEquals("size", 2, replicatedLogImpl.size());
238         assertEquals("getSnapshotIndex", 1, replicatedLogImpl.getSnapshotIndex());
239         assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm());
240
241         replicatedLogImpl.snapshotRollback();
242
243         assertEquals("size", 4, replicatedLogImpl.size());
244         assertEquals("dataSize", 4, replicatedLogImpl.dataSize());
245         assertEquals("getSnapshotIndex", -1, replicatedLogImpl.getSnapshotIndex());
246         assertEquals("getSnapshotTerm", -1, replicatedLogImpl.getSnapshotTerm());
247         Assert.assertNotNull("get(0)", replicatedLogImpl.get(0));
248         Assert.assertNotNull("get(3)", replicatedLogImpl.get(3));
249     }
250
251     @Test
252     public void testIsPresent() {
253         assertTrue(replicatedLogImpl.isPresent(0));
254         assertTrue(replicatedLogImpl.isPresent(1));
255         assertTrue(replicatedLogImpl.isPresent(2));
256         assertTrue(replicatedLogImpl.isPresent(3));
257
258         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("D")));
259         replicatedLogImpl.snapshotPreCommit(3, 2); //snapshot on 3
260         replicatedLogImpl.snapshotCommit();
261
262         assertFalse(replicatedLogImpl.isPresent(0));
263         assertFalse(replicatedLogImpl.isPresent(1));
264         assertFalse(replicatedLogImpl.isPresent(2));
265         assertFalse(replicatedLogImpl.isPresent(3));
266         assertTrue(replicatedLogImpl.isPresent(4));
267
268         replicatedLogImpl.snapshotPreCommit(4, 2); //snapshot on 4
269         replicatedLogImpl.snapshotCommit();
270         assertFalse(replicatedLogImpl.isPresent(4));
271
272         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("D")));
273         assertTrue(replicatedLogImpl.isPresent(5));
274     }
275
276     @Test
277     public void testRemoveFrom() {
278
279         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("E", 2)));
280         replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("F", 3)));
281
282         assertEquals("dataSize", 9, replicatedLogImpl.dataSize());
283
284         long adjusted = replicatedLogImpl.removeFrom(4);
285         assertEquals("removeFrom - adjusted", 4, adjusted);
286         assertEquals("size", 4, replicatedLogImpl.size());
287         assertEquals("dataSize", 4, replicatedLogImpl.dataSize());
288
289         takeSnapshot(1);
290
291         adjusted = replicatedLogImpl.removeFrom(2);
292         assertEquals("removeFrom - adjusted", 1, adjusted);
293         assertEquals("size", 1, replicatedLogImpl.size());
294         assertEquals("dataSize", 1, replicatedLogImpl.dataSize());
295
296         assertEquals("removeFrom - adjusted", -1, replicatedLogImpl.removeFrom(0));
297         assertEquals("removeFrom - adjusted", -1, replicatedLogImpl.removeFrom(100));
298     }
299
300     // create a snapshot for test
301     public Map<Long, String> takeSnapshot(final int numEntries) {
302         Map<Long, String> map = new HashMap<>(numEntries);
303
304         long lastIndex = 0;
305         long lastTerm = 0;
306         for(int i = 0; i < numEntries; i++) {
307             ReplicatedLogEntry entry = replicatedLogImpl.getAtPhysicalIndex(i);
308             map.put(entry.getIndex(), entry.getData().toString());
309             lastIndex = entry.getIndex();
310             lastTerm = entry.getTerm();
311         }
312
313         replicatedLogImpl.snapshotPreCommit(lastIndex, lastTerm);
314         replicatedLogImpl.snapshotCommit();
315
316         return map;
317
318     }
319     class MockAbstractReplicatedLogImpl extends AbstractReplicatedLogImpl {
320         @Override
321         public void appendAndPersist(final ReplicatedLogEntry replicatedLogEntry) {
322         }
323
324         @Override
325         public void removeFromAndPersist(final long index) {
326         }
327
328         @Override
329         public void appendAndPersist(ReplicatedLogEntry replicatedLogEntry, Procedure<ReplicatedLogEntry> callback) {
330         }
331
332         @Override
333         public void captureSnapshotIfReady(ReplicatedLogEntry replicatedLogEntry) {
334         }
335
336
337     }
338 }