2 * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.cluster.raft;
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;
43 * Unit tests for RaftActorRecoverySupport.
45 * @author Thomas Pantelis
47 public class RaftActorRecoverySupportTest {
49 private static final Logger LOG = LoggerFactory.getLogger(RaftActorRecoverySupportTest.class);
52 private DataPersistenceProvider mockPersistence;
55 private RaftActorBehavior mockBehavior;
58 private RaftActorRecoveryCohort mockCohort;
61 PersistentDataProvider mockPersistentProvider;
63 private RaftActorRecoverySupport support;
65 private RaftActorContext context;
66 private final DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl();
70 MockitoAnnotations.initMocks(this);
72 context = new RaftActorContextImpl(null, null, "test", new ElectionTermImpl(mockPersistentProvider, "test", LOG),
73 -1, -1, Collections.<String,String>emptyMap(), configParams, mockPersistence, LOG);
75 support = new RaftActorRecoverySupport(context, mockBehavior , mockCohort);
77 doReturn(true).when(mockPersistence).isRecoveryApplicable();
79 context.setReplicatedLog(ReplicatedLogImpl.newInstance(context, mockBehavior));
82 private void sendMessageToSupport(Object message) {
83 sendMessageToSupport(message, false);
86 private void sendMessageToSupport(Object message, boolean expComplete) {
87 boolean complete = support.handleRecoveryMessage(message, mockPersistentProvider);
88 assertEquals("complete", expComplete, complete);
92 public void testOnReplicatedLogEntry() {
93 MockRaftActorContext.MockReplicatedLogEntry logEntry = new MockRaftActorContext.MockReplicatedLogEntry(1,
94 1, new MockRaftActorContext.MockPayload("1", 5));
96 sendMessageToSupport(logEntry);
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());
108 public void testOnApplyJournalEntries() {
109 configParams.setJournalRecoveryLogBatchSize(5);
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")));
125 sendMessageToSupport(new ApplyJournalEntries(2));
127 assertEquals("Last applied", 2, context.getLastApplied());
128 assertEquals("Commit index", 2, context.getCommitIndex());
130 sendMessageToSupport(new ApplyJournalEntries(4));
132 assertEquals("Last applied", 4, context.getLastApplied());
133 assertEquals("Last applied", 4, context.getLastApplied());
135 sendMessageToSupport(new ApplyJournalEntries(5));
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());
143 InOrder inOrder = Mockito.inOrder(mockCohort);
144 inOrder.verify(mockCohort).startLogRecoveryBatch(5);
146 for(int i = 0; i < replicatedLog.size() - 1; i++) {
147 inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(i).getData());
150 inOrder.verify(mockCohort).applyCurrentLogRecoveryBatch();
151 inOrder.verify(mockCohort).startLogRecoveryBatch(5);
152 inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(replicatedLog.size() - 1).getData());
154 inOrder.verifyNoMoreInteractions();
158 public void testOnApplyLogEntries() {
159 ReplicatedLog replicatedLog = context.getReplicatedLog();
160 replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,
161 0, new MockRaftActorContext.MockPayload("0")));
163 sendMessageToSupport(new ApplyLogEntries(0));
165 assertEquals("Last applied", 0, context.getLastApplied());
166 assertEquals("Commit index", 0, context.getCommitIndex());
170 public void testOnSnapshotOffer() {
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")));
180 byte[] snapshotBytes = {1,2,3,4,5};
182 ReplicatedLogEntry unAppliedEntry1 = new MockRaftActorContext.MockReplicatedLogEntry(1,
183 4, new MockRaftActorContext.MockPayload("4", 4));
185 ReplicatedLogEntry unAppliedEntry2 = new MockRaftActorContext.MockReplicatedLogEntry(1,
186 5, new MockRaftActorContext.MockPayload("5", 5));
188 long lastAppliedDuringSnapshotCapture = 3;
189 long lastIndexDuringSnapshotCapture = 5;
190 long electionTerm = 2;
191 String electionVotedFor = "member-2";
193 Snapshot snapshot = Snapshot.create(snapshotBytes, Arrays.asList(unAppliedEntry1, unAppliedEntry2),
194 lastIndexDuringSnapshotCapture, 1, lastAppliedDuringSnapshotCapture, 1, electionTerm, electionVotedFor);
196 SnapshotMetadata metadata = new SnapshotMetadata("test", 6, 12345);
197 SnapshotOffer snapshotOffer = new SnapshotOffer(metadata , snapshot);
199 sendMessageToSupport(snapshotOffer);
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());
211 verify(mockCohort).applyRecoverySnapshot(snapshotBytes);
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")));
222 sendMessageToSupport(new ApplyJournalEntries(1));
224 sendMessageToSupport(RecoveryCompleted.getInstance(), true);
226 assertEquals("Last applied", 1, context.getLastApplied());
227 assertEquals("Commit index", 1, context.getCommitIndex());
229 InOrder inOrder = Mockito.inOrder(mockCohort);
230 inOrder.verify(mockCohort).startLogRecoveryBatch(anyInt());
232 for(int i = 0; i < replicatedLog.size(); i++) {
233 inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(i).getData());
236 inOrder.verify(mockCohort).applyCurrentLogRecoveryBatch();
238 inOrder.verifyNoMoreInteractions();
242 public void testOnRecoveryCompletedWithNoRemainingBatch() {
243 sendMessageToSupport(RecoveryCompleted.getInstance(), true);
245 verifyNoMoreInteractions(mockCohort);
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")));
258 sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries(1));
260 assertEquals("Journal log size", 1, context.getReplicatedLog().size());
261 assertEquals("Last index", 0, context.getReplicatedLog().lastIndex());
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")));
274 sendMessageToSupport(new DeleteEntries(1));
276 assertEquals("Journal log size", 1, context.getReplicatedLog().size());
277 assertEquals("Last index", 0, context.getReplicatedLog().lastIndex());
281 public void testUpdateElectionTerm() {
283 sendMessageToSupport(new UpdateElectionTerm(5, "member2"));
285 assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
286 assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
290 public void testDeprecatedUpdateElectionTerm() {
292 sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.UpdateElectionTerm(5, "member2"));
294 assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
295 assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
298 @SuppressWarnings("unchecked")
300 public void testDataRecoveredWithPersistenceDisabled() {
301 doReturn(false).when(mockPersistence).isRecoveryApplicable();
302 doReturn(10L).when(mockPersistentProvider).getLastSequenceNumber();
304 sendMessageToSupport(new UpdateElectionTerm(5, "member2"));
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);
309 sendMessageToSupport(snapshotOffer);
311 sendMessageToSupport(new MockRaftActorContext.MockReplicatedLogEntry(1,
312 4, new MockRaftActorContext.MockPayload("4")));
313 sendMessageToSupport(new MockRaftActorContext.MockReplicatedLogEntry(1,
314 5, new MockRaftActorContext.MockPayload("5")));
316 sendMessageToSupport(new ApplyJournalEntries(4));
318 sendMessageToSupport(new DeleteEntries(5));
320 sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries(5));
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());
329 assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
330 assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
332 sendMessageToSupport(RecoveryCompleted.getInstance(), true);
334 verifyNoMoreInteractions(mockCohort);
336 verify(mockPersistentProvider).deleteMessages(10L);
337 verify(mockPersistentProvider).deleteSnapshots(any(SnapshotSelectionCriteria.class));
338 verify(mockPersistentProvider).persist(updateElectionTerm(5, "member2"), any(Procedure.class));
341 static UpdateElectionTerm updateElectionTerm(final long term, final String votedFor) {
342 return Matchers.argThat(new ArgumentMatcher<UpdateElectionTerm>() {
344 public boolean matches(Object argument) {
345 UpdateElectionTerm other = (UpdateElectionTerm) argument;
346 return term == other.getCurrentTerm() && votedFor.equals(other.getVotedFor());
350 public void describeTo(Description description) {
351 description.appendValue(new UpdateElectionTerm(term, votedFor));
357 public void testNoDataRecoveredWithPersistenceDisabled() {
358 doReturn(false).when(mockPersistence).isRecoveryApplicable();
360 sendMessageToSupport(new UpdateElectionTerm(5, "member2"));
362 assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm());
363 assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor());
365 sendMessageToSupport(RecoveryCompleted.getInstance(), true);
367 verifyNoMoreInteractions(mockCohort, mockPersistentProvider);