Fix mdsal-dom-spi dependencies
[mdsal.git] / dom / mdsal-dom-spi / src / test / java / org / opendaylight / mdsal / dom / spi / PingPongTransactionChainTest.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.mdsal.dom.spi;
9
10 import static org.hamcrest.CoreMatchers.allOf;
11 import static org.hamcrest.CoreMatchers.containsString;
12 import static org.hamcrest.CoreMatchers.endsWith;
13 import static org.hamcrest.CoreMatchers.startsWith;
14 import static org.hamcrest.MatcherAssert.assertThat;
15 import static org.junit.Assert.assertEquals;
16 import static org.junit.Assert.assertFalse;
17 import static org.junit.Assert.assertNotNull;
18 import static org.junit.Assert.assertSame;
19 import static org.junit.Assert.assertThrows;
20 import static org.mockito.Mockito.doNothing;
21 import static org.mockito.Mockito.doReturn;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.verify;
25
26 import com.google.common.util.concurrent.FluentFuture;
27 import com.google.common.util.concurrent.FutureCallback;
28 import com.google.common.util.concurrent.Futures;
29 import com.google.common.util.concurrent.SettableFuture;
30 import java.util.Optional;
31 import java.util.concurrent.ExecutionException;
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.mockito.Answers;
36 import org.mockito.Mock;
37 import org.mockito.junit.MockitoJUnitRunner;
38 import org.opendaylight.mdsal.common.api.CommitInfo;
39 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
40 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
41 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
42 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
43 import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
44 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
45 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
46 import org.opendaylight.yangtools.util.concurrent.FluentFutures;
47 import org.opendaylight.yangtools.yang.common.Empty;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
50
51 @RunWith(MockitoJUnitRunner.StrictStubs.class)
52 public class PingPongTransactionChainTest {
53     @Mock
54     public FutureCallback<Empty> listener;
55     @Mock(answer = Answers.CALLS_REAL_METHODS)
56     public DOMTransactionChain chain;
57     @Mock
58     public DOMDataTreeReadWriteTransaction rwTx;
59     @Mock
60     public DOMDataTreeReadWriteTransaction rwTx1;
61     @Mock
62     public DOMDataTreeReadWriteTransaction rwTx2;
63
64     private final SettableFuture<Empty> future = SettableFuture.create();
65
66     public PingPongTransactionChain pingPong;
67
68     @Before
69     public void before() {
70         doReturn(future).when(chain).future();
71         pingPong = new PingPongTransactionChain(chain);
72         doReturn(rwTx).when(chain).newReadWriteTransaction();
73     }
74
75     @Test
76     public void testIdleClose() {
77         doNothing().when(chain).close();
78         pingPong.close();
79         verify(chain).close();
80         pingPong.addCallback(listener);
81
82         future.set(Empty.value());
83         verify(listener).onSuccess(Empty.value());
84     }
85
86     @Test
87     public void testIdleFailure() {
88         final var cause = new Throwable();
89         doNothing().when(listener).onFailure(cause);
90         doReturn("mock").when(chain).toString();
91
92         future.setException(cause);
93         pingPong.addCallback(listener);
94         verify(listener).onFailure(cause);
95     }
96
97     @Test
98     public void testReadOnly() {
99         final var tx = pingPong.newReadOnlyTransaction();
100         assertGetIdentifier(tx);
101         assertReadOperations(tx);
102         assertCommit(tx::close);
103     }
104
105     @Test
106     public void testReadWrite() {
107         final var tx = pingPong.newReadWriteTransaction();
108         assertGetIdentifier(tx);
109         assertReadOperations(tx);
110         assertWriteOperations(tx);
111         assertCommit(tx::commit);
112     }
113
114     @Test
115     public void testWriteOnly() {
116         final var tx = pingPong.newWriteOnlyTransaction();
117         assertGetIdentifier(tx);
118         assertWriteOperations(tx);
119         assertCommit(tx::commit);
120     }
121
122     private void assertGetIdentifier(final DOMDataTreeTransaction tx) {
123         final var id = mock(Object.class);
124         doReturn(id).when(rwTx).getIdentifier();
125         assertSame(id, tx.getIdentifier());
126     }
127
128     private void assertReadOperations(final DOMDataTreeReadOperations tx) {
129         doReturn(FluentFutures.immediateTrueFluentFuture()).when(rwTx).exists(
130             LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
131         final var exists = tx.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
132         verify(rwTx).exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
133         assertEquals(Boolean.TRUE, assertDone(exists));
134
135         doReturn(FluentFutures.immediateFluentFuture(Optional.empty())).when(rwTx).read(
136             LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
137         final var read = tx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
138         verify(rwTx).read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
139         assertEquals(Optional.empty(), assertDone(read));
140     }
141
142     private void assertWriteOperations(final DOMDataTreeWriteOperations tx) {
143         doNothing().when(rwTx).delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
144         tx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
145         verify(rwTx).delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
146
147         final var data = mock(ContainerNode.class);
148         doNothing().when(rwTx).merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of(), data);
149         tx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of(), data);
150         verify(rwTx).merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of(), data);
151
152         doNothing().when(rwTx).put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of(), data);
153         tx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of(), data);
154         verify(rwTx).put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of(), data);
155     }
156
157     private void assertCommit(final Runnable commitMethod) {
158         doReturn(CommitInfo.emptyFluentFuture()).when(rwTx).commit();
159         commitMethod.run();
160         verify(rwTx).commit();
161     }
162
163     @Test
164     public void testCommitFailure() {
165         assertCommitFailure(() -> { });
166     }
167
168     @Test
169     public void testCommitFailureAfterClose() {
170         assertCommitFailure(() -> {
171             doNothing().when(chain).close();
172             pingPong.close();
173             verify(chain).close();
174         });
175     }
176
177     private void assertCommitFailure(final Runnable asyncAction) {
178         final var tx = pingPong.newWriteOnlyTransaction();
179
180         final var rwTxFuture = SettableFuture.<CommitInfo>create();
181         doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
182
183         final var txFuture = tx.commit();
184         verify(rwTx).commit();
185         assertFalse(txFuture.isDone());
186
187         asyncAction.run();
188
189         final var cause = new TransactionCommitFailedException("cause");
190         rwTxFuture.setException(cause);
191         assertSame(cause, assertThrows(ExecutionException.class, () -> Futures.getDone(txFuture)).getCause());
192     }
193
194     @Test
195     public void testSimpleCancelFalse() {
196         assertSimpleCancel(false);
197     }
198
199     @Test
200     public void testSimpleCancelTrue() {
201         assertSimpleCancel(true);
202     }
203
204     private void assertSimpleCancel(final boolean result) {
205         final var tx = pingPong.newWriteOnlyTransaction();
206
207         doReturn(result).when(rwTx).cancel();
208         assertEquals(result, tx.cancel());
209         verify(rwTx).cancel();
210     }
211
212     @Test
213     public void testNewAfterSuccessfulCancel() {
214         doReturn(true).when(rwTx).cancel();
215         pingPong.newWriteOnlyTransaction().cancel();
216         assertNotNull(pingPong.newWriteOnlyTransaction());
217     }
218
219     @Test
220     public void testNewAfterNew() {
221         assertNotNull(pingPong.newWriteOnlyTransaction());
222         doReturn(true).when(rwTx).cancel();
223         doReturn("mock").when(rwTx).toString();
224         final var ex = assertThrows(IllegalStateException.class, () -> pingPong.newWriteOnlyTransaction());
225         assertThat(ex.getMessage(), allOf(
226             startsWith("New transaction PingPongTransaction"),
227             containsString(" raced with transaction PingPongTransaction")));
228     }
229
230     @Test
231     public void testReadWriteReuse() {
232         final var tx = pingPong.newReadWriteTransaction();
233         final var rwTxFuture = SettableFuture.<CommitInfo>create();
234         doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
235         // Now rwTx is inflight, but does not commit immediately
236         final var txFuture = tx.commit();
237         verify(rwTx).commit();
238
239         // Assert identity without delving into details
240         final var id = mock(Object.class);
241         doReturn(id).when(rwTx).getIdentifier();
242         assertSame(tx.getIdentifier(), id);
243
244         doReturn(rwTx1).when(chain).newReadWriteTransaction();
245         final var tx1 = pingPong.newWriteOnlyTransaction();
246         // now rwTx1 is ready, waiting for inflight to be completed
247         final var tx1Future = tx1.commit();
248
249         final var id1 = mock(Object.class);
250         doReturn(id1).when(rwTx1).getIdentifier();
251         assertSame(tx1.getIdentifier(), id1);
252
253         // Ready transaction is picked up by fast path allocation
254         final var tx2 = pingPong.newWriteOnlyTransaction();
255         assertSame(tx2.getIdentifier(), id1);
256
257         // Complete inflight transaction...
258         rwTxFuture.set(CommitInfo.empty());
259         assertDone(txFuture);
260         // ... but we are still holding the follow-up frontend transaction ...
261         assertFalse(tx1Future.isDone());
262         verify(rwTx1, never()).commit();
263
264         // ... and it will commit once we commit tx2 ...
265         doReturn(CommitInfo.emptyFluentFuture()).when(rwTx1).commit();
266         final var tx2Future = tx2.commit();
267         // ... at which point both complete
268         assertDone(tx1Future);
269         assertDone(tx2Future);
270     }
271
272     @Test
273     public void commitWhileInflight() {
274         final var tx = pingPong.newReadWriteTransaction();
275
276         final var rwTxFuture = SettableFuture.<CommitInfo>create();
277         doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
278         // rwTxFuture is inflight
279         final var txFuture = tx.commit();
280         verify(rwTx).commit();
281         assertFalse(txFuture.isDone());
282
283         doReturn(rwTx1).when(chain).newReadWriteTransaction();
284         final var rwTxFuture1 = SettableFuture.<CommitInfo>create();
285         final var tx1 = pingPong.newWriteOnlyTransaction();
286         final var tx1Future = tx1.commit();
287
288         doReturn(FluentFuture.from(rwTxFuture1)).when(rwTx1).commit();
289         rwTxFuture.set(CommitInfo.empty());
290         assertDone(txFuture);
291         verify(rwTx1).commit();
292
293         rwTxFuture1.set(CommitInfo.empty());
294         assertDone(tx1Future);
295     }
296
297     @Test
298     public void testNewAfterAsyncShutdown() {
299         // Setup inflight transaction
300         final var tx = pingPong.newReadWriteTransaction();
301         final var rwTxFuture = SettableFuture.<CommitInfo>create();
302         doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
303         final var txFuture = tx.commit();
304         assertFalse(txFuture.isDone());
305
306         // Setup ready transaction
307         doReturn(rwTx1).when(chain).newReadWriteTransaction();
308         final var rwTx1Future = SettableFuture.<CommitInfo>create();
309         doReturn(FluentFuture.from(rwTx1Future)).when(rwTx1).commit();
310
311         final var tx1Future = pingPong.newReadWriteTransaction().commit();
312         assertFalse(tx1Future.isDone());
313
314         pingPong.close();
315
316         final var ex = assertThrows(IllegalStateException.class, pingPong::newWriteOnlyTransaction);
317         assertThat(ex.getMessage(), allOf(startsWith("Transaction chain "), endsWith(" has been shut down")));
318         doNothing().when(chain).close();
319         rwTxFuture.set(CommitInfo.empty());
320         assertDone(txFuture);
321         verify(chain).close();
322
323         rwTx1Future.set(CommitInfo.empty());
324         assertDone(tx1Future);
325     }
326
327     @Test
328     public void testIdempotentClose() {
329         doNothing().when(chain).close();
330         pingPong.close();
331         verify(chain).close();
332         pingPong.close();
333 //        verifyNoMoreInteractions(chain);
334     }
335
336     private static <T> T assertDone(final FluentFuture<T> future) {
337         try {
338             return Futures.getDone(future);
339         } catch (ExecutionException e) {
340             throw new AssertionError(e);
341         }
342     }
343 }