Add election info to Snapshot
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / RaftActorRecoverySupportTest.java
1 /*
2  * Copyright (c) 2015 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.cluster.raft;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.mockito.Matchers.any;
12 import static org.mockito.Matchers.anyInt;
13 import static org.mockito.Mockito.doReturn;
14 import static org.mockito.Mockito.verify;
15 import static org.mockito.Mockito.verifyNoMoreInteractions;
16 import akka.japi.Procedure;
17 import akka.persistence.RecoveryCompleted;
18 import akka.persistence.SnapshotMetadata;
19 import akka.persistence.SnapshotOffer;
20 import akka.persistence.SnapshotSelectionCriteria;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import org.hamcrest.Description;
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.mockito.ArgumentMatcher;
27 import org.mockito.InOrder;
28 import org.mockito.Matchers;
29 import org.mockito.Mock;
30 import org.mockito.Mockito;
31 import org.mockito.MockitoAnnotations;
32 import org.opendaylight.controller.cluster.DataPersistenceProvider;
33 import org.opendaylight.controller.cluster.PersistentDataProvider;
34 import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries;
35 import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries;
36 import org.opendaylight.controller.cluster.raft.base.messages.DeleteEntries;
37 import org.opendaylight.controller.cluster.raft.base.messages.UpdateElectionTerm;
38 import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Unit tests for RaftActorRecoverySupport.
44  *
45  * @author Thomas Pantelis
46  */
47 public class RaftActorRecoverySupportTest {
48
49     private static final Logger LOG = LoggerFactory.getLogger(RaftActorRecoverySupportTest.class);
50
51     @Mock
52     private DataPersistenceProvider mockPersistence;
53
54     @Mock
55     private RaftActorBehavior mockBehavior;
56
57     @Mock
58     private RaftActorRecoveryCohort mockCohort;
59
60     @Mock
61     PersistentDataProvider mockPersistentProvider;
62
63     private RaftActorRecoverySupport support;
64
65     private RaftActorContext context;
66     private final DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl();
67
68     @Before
69     public void setup() {
70         MockitoAnnotations.initMocks(this);
71
72         context = new RaftActorContextImpl(null, null, "test", new ElectionTermImpl(mockPersistentProvider, "test", LOG),
73                 -1, -1, Collections.<String,String>emptyMap(), configParams, mockPersistence, LOG);
74
75         support = new RaftActorRecoverySupport(context, mockBehavior , mockCohort);
76
77         doReturn(true).when(mockPersistence).isRecoveryApplicable();
78
79         context.setReplicatedLog(ReplicatedLogImpl.newInstance(context, mockBehavior));
80     }
81
82     private void sendMessageToSupport(Object message) {
83         sendMessageToSupport(message, false);
84     }
85
86     private void sendMessageToSupport(Object message, boolean expComplete) {
87         boolean complete = support.handleRecoveryMessage(message, mockPersistentProvider);
88         assertEquals("complete", expComplete, complete);
89     }
90
91     @Test
92     public void testOnReplicatedLogEntry() {
93         MockRaftActorContext.MockReplicatedLogEntry logEntry = new MockRaftActorContext.MockReplicatedLogEntry(1,
94                 1, new MockRaftActorContext.MockPayload("1", 5));
95
96         sendMessageToSupport(logEntry);
97
98         assertEquals("Journal log size", 1, context.getReplicatedLog().size());
99         assertEquals("Journal data size", 5, context.getReplicatedLog().dataSize());
100         assertEquals("Last index", 1, context.getReplicatedLog().lastIndex());
101         assertEquals("Last applied", -1, context.getLastApplied());
102         assertEquals("Commit index", -1, context.getCommitIndex());
103         assertEquals("Snapshot term", -1, context.getReplicatedLog().getSnapshotTerm());
104         assertEquals("Snapshot index", -1, context.getReplicatedLog().getSnapshotIndex());
105     }
106
107     @Test
108     public void testOnApplyJournalEntries() {
109         configParams.setJournalRecoveryLogBatchSize(5);
110
111         ReplicatedLog replicatedLog = context.getReplicatedLog();
112         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
113                 0, new MockRaftActorContext.MockPayload("0")));
114         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
115                 1, new MockRaftActorContext.MockPayload("1")));
116         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
117                 2, new MockRaftActorContext.MockPayload("2")));
118         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
119                 3, new MockRaftActorContext.MockPayload("3")));
120         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
121                 4, new MockRaftActorContext.MockPayload("4")));
122         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
123                 5, new MockRaftActorContext.MockPayload("5")));
124
125         sendMessageToSupport(new ApplyJournalEntries(2));
126
127         assertEquals("Last applied", 2, context.getLastApplied());
128         assertEquals("Commit index", 2, context.getCommitIndex());
129
130         sendMessageToSupport(new ApplyJournalEntries(4));
131
132         assertEquals("Last applied", 4, context.getLastApplied());
133         assertEquals("Last applied", 4, context.getLastApplied());
134
135         sendMessageToSupport(new ApplyJournalEntries(5));
136
137         assertEquals("Last index", 5, context.getReplicatedLog().lastIndex());
138         assertEquals("Last applied", 5, context.getLastApplied());
139         assertEquals("Commit index", 5, context.getCommitIndex());
140         assertEquals("Snapshot term", -1, context.getReplicatedLog().getSnapshotTerm());
141         assertEquals("Snapshot index", -1, context.getReplicatedLog().getSnapshotIndex());
142
143         InOrder inOrder = Mockito.inOrder(mockCohort);
144         inOrder.verify(mockCohort).startLogRecoveryBatch(5);
145
146         for(int i = 0; i < replicatedLog.size() - 1; i++) {
147             inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(i).getData());
148         }
149
150         inOrder.verify(mockCohort).applyCurrentLogRecoveryBatch();
151         inOrder.verify(mockCohort).startLogRecoveryBatch(5);
152         inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(replicatedLog.size() - 1).getData());
153
154         inOrder.verifyNoMoreInteractions();
155     }
156
157     @Test
158     public void testOnApplyLogEntries() {
159         ReplicatedLog replicatedLog = context.getReplicatedLog();
160         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
161                 0, new MockRaftActorContext.MockPayload("0")));
162
163         sendMessageToSupport(new ApplyLogEntries(0));
164
165         assertEquals("Last applied", 0, context.getLastApplied());
166         assertEquals("Commit index", 0, context.getCommitIndex());
167     }
168
169     @Test
170     public void testOnSnapshotOffer() {
171
172         ReplicatedLog replicatedLog = context.getReplicatedLog();
173         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
174                 1, new MockRaftActorContext.MockPayload("1")));
175         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
176                 2, new MockRaftActorContext.MockPayload("2")));
177         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
178                 3, new MockRaftActorContext.MockPayload("3")));
179
180         byte[] snapshotBytes = {1,2,3,4,5};
181
182         ReplicatedLogEntry unAppliedEntry1 = new MockRaftActorContext.MockReplicatedLogEntry(1,
183                 4, new MockRaftActorContext.MockPayload("4", 4));
184
185         ReplicatedLogEntry unAppliedEntry2 = new MockRaftActorContext.MockReplicatedLogEntry(1,
186                 5, new MockRaftActorContext.MockPayload("5", 5));
187
188         long lastAppliedDuringSnapshotCapture = 3;
189         long lastIndexDuringSnapshotCapture = 5;
190         long electionTerm = 2;
191         String electionVotedFor = "member-2";
192
193         Snapshot snapshot = Snapshot.create(snapshotBytes, Arrays.asList(unAppliedEntry1, unAppliedEntry2),
194                 lastIndexDuringSnapshotCapture, 1, lastAppliedDuringSnapshotCapture, 1, electionTerm, electionVotedFor);
195
196         SnapshotMetadata metadata = new SnapshotMetadata("test", 6, 12345);
197         SnapshotOffer snapshotOffer = new SnapshotOffer(metadata , snapshot);
198
199         sendMessageToSupport(snapshotOffer);
200
201         assertEquals("Journal log size", 2, context.getReplicatedLog().size());
202         assertEquals("Journal data size", 9, context.getReplicatedLog().dataSize());
203         assertEquals("Last index", lastIndexDuringSnapshotCapture, context.getReplicatedLog().lastIndex());
204         assertEquals("Last applied", lastAppliedDuringSnapshotCapture, context.getLastApplied());
205         assertEquals("Commit index", lastAppliedDuringSnapshotCapture, context.getCommitIndex());
206         assertEquals("Snapshot term", 1, context.getReplicatedLog().getSnapshotTerm());
207         assertEquals("Snapshot index", lastAppliedDuringSnapshotCapture, context.getReplicatedLog().getSnapshotIndex());
208         assertEquals("Election term", electionTerm, context.getTermInformation().getCurrentTerm());
209         assertEquals("Election votedFor", electionVotedFor, context.getTermInformation().getVotedFor());
210
211         verify(mockCohort).applyRecoverySnapshot(snapshotBytes);
212     }
213
214     @Test
215     public void testOnRecoveryCompletedWithRemainingBatch() {
216         ReplicatedLog replicatedLog = context.getReplicatedLog();
217         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
218                 0, new MockRaftActorContext.MockPayload("0")));
219         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
220                 1, new MockRaftActorContext.MockPayload("1")));
221
222         sendMessageToSupport(new ApplyJournalEntries(1));
223
224         sendMessageToSupport(RecoveryCompleted.getInstance(), true);
225
226         assertEquals("Last applied", 1, context.getLastApplied());
227         assertEquals("Commit index", 1, context.getCommitIndex());
228
229         InOrder inOrder = Mockito.inOrder(mockCohort);
230         inOrder.verify(mockCohort).startLogRecoveryBatch(anyInt());
231
232         for(int i = 0; i < replicatedLog.size(); i++) {
233             inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(i).getData());
234         }
235
236         inOrder.verify(mockCohort).applyCurrentLogRecoveryBatch();
237
238         inOrder.verifyNoMoreInteractions();
239     }
240
241     @Test
242     public void testOnRecoveryCompletedWithNoRemainingBatch() {
243         sendMessageToSupport(RecoveryCompleted.getInstance(), true);
244
245         verifyNoMoreInteractions(mockCohort);
246     }
247
248     @Test
249     public void testOnDeprecatedDeleteEntries() {
250         ReplicatedLog replicatedLog = context.getReplicatedLog();
251         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
252                 0, new MockRaftActorContext.MockPayload("0")));
253         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
254                 1, new MockRaftActorContext.MockPayload("1")));
255         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
256                 2, new MockRaftActorContext.MockPayload("2")));
257
258         sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries(1));
259
260         assertEquals("Journal log size", 1, context.getReplicatedLog().size());
261         assertEquals("Last index", 0, context.getReplicatedLog().lastIndex());
262     }
263
264     @Test
265     public void testOnDeleteEntries() {
266         ReplicatedLog replicatedLog = context.getReplicatedLog();
267         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
268                 0, new MockRaftActorContext.MockPayload("0")));
269         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
270                 1, new MockRaftActorContext.MockPayload("1")));
271         replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
272                 2, new MockRaftActorContext.MockPayload("2")));
273
274         sendMessageToSupport(new DeleteEntries(1));
275
276         assertEquals("Journal log size", 1, context.getReplicatedLog().size());
277         assertEquals("Last index", 0, context.getReplicatedLog().lastIndex());
278     }
279
280     @Test
281     public void testUpdateElectionTerm() {
282
283         sendMessageToSupport(new UpdateElectionTerm(5, "member2"));
284
285         assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
286         assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
287     }
288
289     @Test
290     public void testDeprecatedUpdateElectionTerm() {
291
292         sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.UpdateElectionTerm(5, "member2"));
293
294         assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
295         assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
296     }
297
298     @SuppressWarnings("unchecked")
299     @Test
300     public void testDataRecoveredWithPersistenceDisabled() {
301         doReturn(false).when(mockPersistence).isRecoveryApplicable();
302         doReturn(10L).when(mockPersistentProvider).getLastSequenceNumber();
303
304         sendMessageToSupport(new UpdateElectionTerm(5, "member2"));
305
306         Snapshot snapshot = Snapshot.create(new byte[]{1}, Collections.<ReplicatedLogEntry>emptyList(), 3, 1, 3, 1);
307         SnapshotOffer snapshotOffer = new SnapshotOffer(new SnapshotMetadata("test", 6, 12345), snapshot);
308
309         sendMessageToSupport(snapshotOffer);
310
311         sendMessageToSupport(new MockRaftActorContext.MockReplicatedLogEntry(1,
312                 4, new MockRaftActorContext.MockPayload("4")));
313         sendMessageToSupport(new MockRaftActorContext.MockReplicatedLogEntry(1,
314                 5, new MockRaftActorContext.MockPayload("5")));
315
316         sendMessageToSupport(new ApplyJournalEntries(4));
317
318         sendMessageToSupport(new DeleteEntries(5));
319
320         sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries(5));
321
322         assertEquals("Journal log size", 0, context.getReplicatedLog().size());
323         assertEquals("Last index", -1, context.getReplicatedLog().lastIndex());
324         assertEquals("Last applied", -1, context.getLastApplied());
325         assertEquals("Commit index", -1, context.getCommitIndex());
326         assertEquals("Snapshot term", -1, context.getReplicatedLog().getSnapshotTerm());
327         assertEquals("Snapshot index", -1, context.getReplicatedLog().getSnapshotIndex());
328
329         assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
330         assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
331
332         sendMessageToSupport(RecoveryCompleted.getInstance(), true);
333
334         verifyNoMoreInteractions(mockCohort);
335
336         verify(mockPersistentProvider).deleteMessages(10L);
337         verify(mockPersistentProvider).deleteSnapshots(any(SnapshotSelectionCriteria.class));
338         verify(mockPersistentProvider).persist(updateElectionTerm(5, "member2"), any(Procedure.class));
339     }
340
341     static UpdateElectionTerm updateElectionTerm(final long term, final String votedFor) {
342         return Matchers.argThat(new ArgumentMatcher<UpdateElectionTerm>() {
343             @Override
344             public boolean matches(Object argument) {
345                 UpdateElectionTerm other = (UpdateElectionTerm) argument;
346                 return term == other.getCurrentTerm() && votedFor.equals(other.getVotedFor());
347             }
348
349             @Override
350             public void describeTo(Description description) {
351                 description.appendValue(new UpdateElectionTerm(term, votedFor));
352             }
353         });
354     }
355
356     @Test
357     public void testNoDataRecoveredWithPersistenceDisabled() {
358         doReturn(false).when(mockPersistence).isRecoveryApplicable();
359
360         sendMessageToSupport(new UpdateElectionTerm(5, "member2"));
361
362         assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
363         assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
364
365         sendMessageToSupport(RecoveryCompleted.getInstance(), true);
366
367         verifyNoMoreInteractions(mockCohort, mockPersistentProvider);
368     }
369 }