Bug 7362: Notify applyState synchronously
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / ReplicatedLogImplTest.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.same;
12 import static org.mockito.Mockito.doReturn;
13 import static org.mockito.Mockito.reset;
14 import static org.mockito.Mockito.verify;
15 import static org.mockito.Mockito.verifyNoMoreInteractions;
16
17 import akka.japi.Procedure;
18 import java.util.Collections;
19 import org.hamcrest.BaseMatcher;
20 import org.hamcrest.Description;
21 import org.hamcrest.Matcher;
22 import org.junit.Before;
23 import org.junit.Test;
24 import org.mockito.ArgumentCaptor;
25 import org.mockito.Matchers;
26 import org.mockito.Mock;
27 import org.mockito.Mockito;
28 import org.mockito.MockitoAnnotations;
29 import org.mockito.internal.matchers.Same;
30 import org.opendaylight.controller.cluster.DataPersistenceProvider;
31 import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload;
32 import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior;
33 import org.opendaylight.controller.cluster.raft.persisted.DeleteEntries;
34 import org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Unit tests for ReplicatedLogImpl.
40  *
41  * @author Thomas Pantelis
42  */
43 public class ReplicatedLogImplTest {
44     private static final Logger LOG = LoggerFactory.getLogger(RaftActorRecoverySupportTest.class);
45
46     @Mock
47     private DataPersistenceProvider mockPersistence;
48
49     @Mock
50     private RaftActorBehavior mockBehavior;
51
52     private RaftActorContext context;
53     private final DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl();
54
55     @Before
56     public void setup() {
57         MockitoAnnotations.initMocks(this);
58
59         context = new RaftActorContextImpl(null, null, "test",
60                 new ElectionTermImpl(mockPersistence, "test", LOG), -1, -1, Collections.<String,String>emptyMap(),
61                 configParams, mockPersistence, applyState -> { }, LOG);
62     }
63
64     private void verifyPersist(Object message) throws Exception {
65         verifyPersist(message, new Same(message), true);
66     }
67
68     @SuppressWarnings({ "unchecked", "rawtypes" })
69     private void verifyPersist(Object message, Matcher<?> matcher, boolean async) throws Exception {
70         ArgumentCaptor<Procedure> procedure = ArgumentCaptor.forClass(Procedure.class);
71         if (async) {
72             verify(mockPersistence).persistAsync(Matchers.argThat(matcher), procedure.capture());
73         } else {
74             verify(mockPersistence).persist(Matchers.argThat(matcher), procedure.capture());
75         }
76
77         procedure.getValue().apply(message);
78     }
79
80     @SuppressWarnings("unchecked")
81     @Test
82     public void testAppendAndPersistExpectingNoCapture() throws Exception {
83         ReplicatedLog log = ReplicatedLogImpl.newInstance(context);
84
85         ReplicatedLogEntry logEntry1 = new SimpleReplicatedLogEntry(1, 1, new MockPayload("1"));
86
87         log.appendAndPersist(logEntry1, null, true);
88
89         verifyPersist(logEntry1);
90
91         assertEquals("size", 1, log.size());
92
93         reset(mockPersistence);
94
95         ReplicatedLogEntry logEntry2 = new SimpleReplicatedLogEntry(2, 1, new MockPayload("2"));
96         Procedure<ReplicatedLogEntry> mockCallback = Mockito.mock(Procedure.class);
97         log.appendAndPersist(logEntry2, mockCallback, true);
98
99         verifyPersist(logEntry2);
100
101         verify(mockCallback).apply(same(logEntry2));
102
103         assertEquals("size", 2, log.size());
104     }
105
106     @SuppressWarnings("unchecked")
107     @Test
108     public void testAppendAndPersisWithDuplicateEntry() throws Exception {
109         ReplicatedLog log = ReplicatedLogImpl.newInstance(context);
110
111         Procedure<ReplicatedLogEntry> mockCallback = Mockito.mock(Procedure.class);
112         ReplicatedLogEntry logEntry = new SimpleReplicatedLogEntry(1, 1, new MockPayload("1"));
113
114         log.appendAndPersist(logEntry, mockCallback, true);
115
116         verifyPersist(logEntry);
117
118         assertEquals("size", 1, log.size());
119
120         reset(mockPersistence, mockCallback);
121
122         log.appendAndPersist(logEntry, mockCallback, true);
123
124         verifyNoMoreInteractions(mockPersistence, mockCallback);
125
126         assertEquals("size", 1, log.size());
127     }
128
129     @Test
130     public void testAppendAndPersistExpectingCaptureDueToJournalCount() throws Exception {
131         configParams.setSnapshotBatchCount(2);
132
133         doReturn(1L).when(mockBehavior).getReplicatedToAllIndex();
134
135         ReplicatedLog log = ReplicatedLogImpl.newInstance(context);
136
137         final ReplicatedLogEntry logEntry1 = new SimpleReplicatedLogEntry(2, 1, new MockPayload("2"));
138         final ReplicatedLogEntry logEntry2 = new SimpleReplicatedLogEntry(3, 1, new MockPayload("3"));
139
140         log.appendAndPersist(logEntry1, null, true);
141         verifyPersist(logEntry1);
142
143         reset(mockPersistence);
144
145         log.appendAndPersist(logEntry2, null, true);
146         verifyPersist(logEntry2);
147
148
149         assertEquals("size", 2, log.size());
150     }
151
152     @Test
153     public void testAppendAndPersistExpectingCaptureDueToDataSize() throws Exception {
154         doReturn(1L).when(mockBehavior).getReplicatedToAllIndex();
155
156         context.setTotalMemoryRetriever(() -> 100);
157
158         ReplicatedLog log = ReplicatedLogImpl.newInstance(context);
159
160         int dataSize = 600;
161         ReplicatedLogEntry logEntry = new SimpleReplicatedLogEntry(2, 1, new MockPayload("2", dataSize));
162
163         log.appendAndPersist(logEntry, null, true);
164         verifyPersist(logEntry);
165
166         reset(mockPersistence);
167
168         logEntry = new SimpleReplicatedLogEntry(3, 1, new MockPayload("3", 5));
169
170         log.appendAndPersist(logEntry, null, true);
171         verifyPersist(logEntry);
172
173         assertEquals("size", 2, log.size());
174     }
175
176     @Test
177     public void testRemoveFromAndPersist() throws Exception {
178
179         ReplicatedLog log = ReplicatedLogImpl.newInstance(context);
180
181         log.append(new SimpleReplicatedLogEntry(0, 1, new MockPayload("0")));
182         log.append(new SimpleReplicatedLogEntry(1, 1, new MockPayload("1")));
183         log.append(new SimpleReplicatedLogEntry(2, 1, new MockPayload("2")));
184
185         log.removeFromAndPersist(1);
186
187         DeleteEntries deleteEntries = new DeleteEntries(1);
188         verifyPersist(deleteEntries, match(deleteEntries), false);
189
190         assertEquals("size", 1, log.size());
191
192         reset(mockPersistence);
193
194         log.removeFromAndPersist(1);
195
196         verifyNoMoreInteractions(mockPersistence);
197     }
198
199     public Matcher<DeleteEntries> match(final DeleteEntries actual) {
200         return new BaseMatcher<DeleteEntries>() {
201             @Override
202             public boolean matches(Object obj) {
203                 DeleteEntries other = (DeleteEntries) obj;
204                 return actual.getFromIndex() == other.getFromIndex();
205             }
206
207             @Override
208             public void describeTo(Description description) {
209                 description.appendText("DeleteEntries: fromIndex: " + actual.getFromIndex());
210             }
211         };
212     }
213 }