2 * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.mdsal.dom.spi;
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;
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;
51 @RunWith(MockitoJUnitRunner.StrictStubs.class)
52 public class PingPongTransactionChainTest {
54 public FutureCallback<Empty> listener;
55 @Mock(answer = Answers.CALLS_REAL_METHODS)
56 public DOMTransactionChain chain;
58 public DOMDataTreeReadWriteTransaction rwTx;
60 public DOMDataTreeReadWriteTransaction rwTx1;
62 public DOMDataTreeReadWriteTransaction rwTx2;
64 private final SettableFuture<Empty> future = SettableFuture.create();
66 public PingPongTransactionChain pingPong;
69 public void before() {
70 doReturn(future).when(chain).future();
71 pingPong = new PingPongTransactionChain(chain);
72 doReturn(rwTx).when(chain).newReadWriteTransaction();
76 public void testIdleClose() {
77 doNothing().when(chain).close();
79 verify(chain).close();
80 pingPong.addCallback(listener);
82 future.set(Empty.value());
83 verify(listener).onSuccess(Empty.value());
87 public void testIdleFailure() {
88 final var cause = new Throwable();
89 doNothing().when(listener).onFailure(cause);
90 doReturn("mock").when(chain).toString();
92 future.setException(cause);
93 pingPong.addCallback(listener);
94 verify(listener).onFailure(cause);
98 public void testReadOnly() {
99 final var tx = pingPong.newReadOnlyTransaction();
100 assertGetIdentifier(tx);
101 assertReadOperations(tx);
102 assertCommit(tx::close);
106 public void testReadWrite() {
107 final var tx = pingPong.newReadWriteTransaction();
108 assertGetIdentifier(tx);
109 assertReadOperations(tx);
110 assertWriteOperations(tx);
111 assertCommit(tx::commit);
115 public void testWriteOnly() {
116 final var tx = pingPong.newWriteOnlyTransaction();
117 assertGetIdentifier(tx);
118 assertWriteOperations(tx);
119 assertCommit(tx::commit);
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());
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));
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));
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());
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);
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);
157 private void assertCommit(final Runnable commitMethod) {
158 doReturn(CommitInfo.emptyFluentFuture()).when(rwTx).commit();
160 verify(rwTx).commit();
164 public void testCommitFailure() {
165 assertCommitFailure(() -> { });
169 public void testCommitFailureAfterClose() {
170 assertCommitFailure(() -> {
171 doNothing().when(chain).close();
173 verify(chain).close();
177 private void assertCommitFailure(final Runnable asyncAction) {
178 final var tx = pingPong.newWriteOnlyTransaction();
180 final var rwTxFuture = SettableFuture.<CommitInfo>create();
181 doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
183 final var txFuture = tx.commit();
184 verify(rwTx).commit();
185 assertFalse(txFuture.isDone());
189 final var cause = new TransactionCommitFailedException("cause");
190 rwTxFuture.setException(cause);
191 assertSame(cause, assertThrows(ExecutionException.class, () -> Futures.getDone(txFuture)).getCause());
195 public void testSimpleCancelFalse() {
196 assertSimpleCancel(false);
200 public void testSimpleCancelTrue() {
201 assertSimpleCancel(true);
204 private void assertSimpleCancel(final boolean result) {
205 final var tx = pingPong.newWriteOnlyTransaction();
207 doReturn(result).when(rwTx).cancel();
208 assertEquals(result, tx.cancel());
209 verify(rwTx).cancel();
213 public void testNewAfterSuccessfulCancel() {
214 doReturn(true).when(rwTx).cancel();
215 pingPong.newWriteOnlyTransaction().cancel();
216 assertNotNull(pingPong.newWriteOnlyTransaction());
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")));
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();
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);
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();
249 final var id1 = mock(Object.class);
250 doReturn(id1).when(rwTx1).getIdentifier();
251 assertSame(tx1.getIdentifier(), id1);
253 // Ready transaction is picked up by fast path allocation
254 final var tx2 = pingPong.newWriteOnlyTransaction();
255 assertSame(tx2.getIdentifier(), id1);
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();
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);
273 public void commitWhileInflight() {
274 final var tx = pingPong.newReadWriteTransaction();
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());
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();
288 doReturn(FluentFuture.from(rwTxFuture1)).when(rwTx1).commit();
289 rwTxFuture.set(CommitInfo.empty());
290 assertDone(txFuture);
291 verify(rwTx1).commit();
293 rwTxFuture1.set(CommitInfo.empty());
294 assertDone(tx1Future);
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());
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();
311 final var tx1Future = pingPong.newReadWriteTransaction().commit();
312 assertFalse(tx1Future.isDone());
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();
323 rwTx1Future.set(CommitInfo.empty());
324 assertDone(tx1Future);
328 public void testIdempotentClose() {
329 doNothing().when(chain).close();
331 verify(chain).close();
333 // verifyNoMoreInteractions(chain);
336 private static <T> T assertDone(final FluentFuture<T> future) {
338 return Futures.getDone(future);
339 } catch (ExecutionException e) {
340 throw new AssertionError(e);