BUG-7033: Fix commit exception due to pipe-lining
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / test / java / org / opendaylight / controller / cluster / datastore / ShardDataTreeTest.java
1 /*
2  * Copyright (c) 2015 Cisco 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
9 package org.opendaylight.controller.cluster.datastore;
10
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.fail;
14 import static org.mockito.Matchers.any;
15 import static org.mockito.Matchers.anyBoolean;
16 import static org.mockito.Matchers.eq;
17 import static org.mockito.Mockito.atLeastOnce;
18 import static org.mockito.Mockito.doNothing;
19 import static org.mockito.Mockito.doReturn;
20 import static org.mockito.Mockito.inOrder;
21 import static org.mockito.Mockito.mock;
22 import static org.mockito.Mockito.never;
23 import static org.mockito.Mockito.reset;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.verifyNoMoreInteractions;
26 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.coordinatedCanCommit;
27 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.coordinatedCommit;
28 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.coordinatedPreCommit;
29 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.immediate3PhaseCommit;
30 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.immediateCanCommit;
31 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.immediateCommit;
32 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.immediatePreCommit;
33
34 import com.google.common.base.Optional;
35 import com.google.common.base.Ticker;
36 import com.google.common.collect.Maps;
37 import com.google.common.primitives.UnsignedLong;
38 import com.google.common.util.concurrent.FutureCallback;
39 import java.math.BigInteger;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.concurrent.ExecutionException;
45 import java.util.function.Consumer;
46 import org.junit.Before;
47 import org.junit.Test;
48 import org.mockito.ArgumentCaptor;
49 import org.mockito.InOrder;
50 import org.mockito.Mockito;
51 import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardStats;
52 import org.opendaylight.controller.cluster.datastore.persisted.CommitTransactionPayload;
53 import org.opendaylight.controller.md.cluster.datastore.model.CarsModel;
54 import org.opendaylight.controller.md.cluster.datastore.model.PeopleModel;
55 import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper;
56 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
57 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
58 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
59 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
60 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
61 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
62 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
63 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
64 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
65 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
66 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
67
68 public class ShardDataTreeTest extends AbstractTest {
69
70     private final Shard mockShard = Mockito.mock(Shard.class);
71     private ShardDataTree shardDataTree;
72     private SchemaContext fullSchema;
73
74     @Before
75     public void setUp() {
76         doReturn(true).when(mockShard).canSkipPayload();
77         doReturn(Ticker.systemTicker()).when(mockShard).ticker();
78         doReturn(Mockito.mock(ShardStats.class)).when(mockShard).getShardMBean();
79
80         fullSchema = SchemaContextHelper.full();
81
82         shardDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
83     }
84
85     @Test
86     public void testWrite() throws ExecutionException, InterruptedException {
87         modify(false, true, true);
88     }
89
90     @Test
91     public void testMerge() throws ExecutionException, InterruptedException {
92         modify(true, true, true);
93     }
94
95     private void modify(final boolean merge, final boolean expectedCarsPresent, final boolean expectedPeoplePresent)
96             throws ExecutionException, InterruptedException {
97
98         assertEquals(fullSchema, shardDataTree.getSchemaContext());
99
100         final ReadWriteShardDataTreeTransaction transaction =
101                 shardDataTree.newReadWriteTransaction(nextTransactionId());
102
103         final DataTreeModification snapshot = transaction.getSnapshot();
104
105         assertNotNull(snapshot);
106
107         if (merge) {
108             snapshot.merge(CarsModel.BASE_PATH, CarsModel.create());
109             snapshot.merge(PeopleModel.BASE_PATH, PeopleModel.create());
110         } else {
111             snapshot.write(CarsModel.BASE_PATH, CarsModel.create());
112             snapshot.write(PeopleModel.BASE_PATH, PeopleModel.create());
113         }
114
115         final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
116
117         immediateCanCommit(cohort);
118         immediatePreCommit(cohort);
119         immediateCommit(cohort);
120
121         final ReadOnlyShardDataTreeTransaction readOnlyShardDataTreeTransaction =
122                 shardDataTree.newReadOnlyTransaction(nextTransactionId());
123
124         final DataTreeSnapshot snapshot1 = readOnlyShardDataTreeTransaction.getSnapshot();
125
126         final Optional<NormalizedNode<?, ?>> optional = snapshot1.readNode(CarsModel.BASE_PATH);
127
128         assertEquals(expectedCarsPresent, optional.isPresent());
129
130         final Optional<NormalizedNode<?, ?>> optional1 = snapshot1.readNode(PeopleModel.BASE_PATH);
131
132         assertEquals(expectedPeoplePresent, optional1.isPresent());
133     }
134
135     @Test
136     public void bug4359AddRemoveCarOnce() throws ExecutionException, InterruptedException {
137         final List<DataTreeCandidate> candidates = new ArrayList<>();
138         candidates.add(addCar(shardDataTree));
139         candidates.add(removeCar(shardDataTree));
140
141         final NormalizedNode<?, ?> expected = getCars(shardDataTree);
142
143         applyCandidates(shardDataTree, candidates);
144
145         final NormalizedNode<?, ?> actual = getCars(shardDataTree);
146
147         assertEquals(expected, actual);
148     }
149
150     @Test
151     public void bug4359AddRemoveCarTwice() throws ExecutionException, InterruptedException {
152         final List<DataTreeCandidate> candidates = new ArrayList<>();
153         candidates.add(addCar(shardDataTree));
154         candidates.add(removeCar(shardDataTree));
155         candidates.add(addCar(shardDataTree));
156         candidates.add(removeCar(shardDataTree));
157
158         final NormalizedNode<?, ?> expected = getCars(shardDataTree);
159
160         applyCandidates(shardDataTree, candidates);
161
162         final NormalizedNode<?, ?> actual = getCars(shardDataTree);
163
164         assertEquals(expected, actual);
165     }
166
167     @Test
168     public void testListenerNotifiedOnApplySnapshot() throws Exception {
169         DOMDataTreeChangeListener listener = mock(DOMDataTreeChangeListener.class);
170         shardDataTree.registerTreeChangeListener(CarsModel.CAR_LIST_PATH.node(CarsModel.CAR_QNAME), listener);
171
172         addCar(shardDataTree, "optima");
173
174         verifyOnDataTreeChanged(listener, dtc -> {
175             assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
176             assertEquals("getRootPath", CarsModel.newCarPath("optima"), dtc.getRootPath());
177         });
178
179         addCar(shardDataTree, "sportage");
180
181         verifyOnDataTreeChanged(listener, dtc -> {
182             assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
183             assertEquals("getRootPath", CarsModel.newCarPath("sportage"), dtc.getRootPath());
184         });
185
186         ShardDataTree newDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
187         addCar(newDataTree, "optima");
188         addCar(newDataTree, "murano");
189
190         shardDataTree.applySnapshot(newDataTree.takeStateSnapshot());
191
192         Map<YangInstanceIdentifier, ModificationType> expChanges = Maps.newHashMap();
193         expChanges.put(CarsModel.newCarPath("optima"), ModificationType.WRITE);
194         expChanges.put(CarsModel.newCarPath("murano"), ModificationType.WRITE);
195         expChanges.put(CarsModel.newCarPath("sportage"), ModificationType.DELETE);
196         verifyOnDataTreeChanged(listener, dtc -> {
197             ModificationType expType = expChanges.remove(dtc.getRootPath());
198             assertNotNull("Got unexpected change for " + dtc.getRootPath(), expType);
199             assertEquals("getModificationType", expType, dtc.getRootNode().getModificationType());
200         });
201
202         if (!expChanges.isEmpty()) {
203             fail("Missing change notifications: " + expChanges);
204         }
205     }
206
207     @Test
208     public void testPipelinedTransactionsWithCoordinatedCommits() throws Exception {
209         doReturn(false).when(mockShard).canSkipPayload();
210
211         final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
212             snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
213
214         final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
215             snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
216
217         NormalizedNode<?, ?> peopleNode = PeopleModel.create();
218         final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot ->
219             snapshot.write(PeopleModel.BASE_PATH, peopleNode));
220
221         YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
222         MapEntryNode carNode = CarsModel.newCarEntry("optima", new BigInteger("100"));
223         final ShardDataTreeCohort cohort4 = newShardDataTreeCohort(snapshot -> snapshot.write(carPath, carNode));
224
225         immediateCanCommit(cohort1);
226         final FutureCallback<Void> canCommitCallback2 = coordinatedCanCommit(cohort2);
227         final FutureCallback<Void> canCommitCallback3 = coordinatedCanCommit(cohort3);
228         final FutureCallback<Void> canCommitCallback4 = coordinatedCanCommit(cohort4);
229
230         final FutureCallback<DataTreeCandidate> preCommitCallback1 = coordinatedPreCommit(cohort1);
231         verify(preCommitCallback1).onSuccess(cohort1.getCandidate());
232         verify(canCommitCallback2).onSuccess(null);
233
234         final FutureCallback<DataTreeCandidate> preCommitCallback2 = coordinatedPreCommit(cohort2);
235         verify(preCommitCallback2).onSuccess(cohort2.getCandidate());
236         verify(canCommitCallback3).onSuccess(null);
237
238         final FutureCallback<DataTreeCandidate> preCommitCallback3 = coordinatedPreCommit(cohort3);
239         verify(preCommitCallback3).onSuccess(cohort3.getCandidate());
240         verify(canCommitCallback4).onSuccess(null);
241
242         final FutureCallback<DataTreeCandidate> preCommitCallback4 = coordinatedPreCommit(cohort4);
243         verify(preCommitCallback4).onSuccess(cohort4.getCandidate());
244
245         final FutureCallback<UnsignedLong> commitCallback2 = coordinatedCommit(cohort2);
246         verify(mockShard, never()).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
247                 anyBoolean());
248         verifyNoMoreInteractions(commitCallback2);
249
250         final FutureCallback<UnsignedLong> commitCallback4 = coordinatedCommit(cohort4);
251         verify(mockShard, never()).persistPayload(eq(cohort4.getIdentifier()), any(CommitTransactionPayload.class),
252                 anyBoolean());
253         verifyNoMoreInteractions(commitCallback4);
254
255         final FutureCallback<UnsignedLong> commitCallback1 = coordinatedCommit(cohort1);
256         InOrder inOrder = inOrder(mockShard);
257         inOrder.verify(mockShard).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
258                 eq(true));
259         inOrder.verify(mockShard).persistPayload(eq(cohort2.getIdentifier()), any(CommitTransactionPayload.class),
260                 eq(false));
261         verifyNoMoreInteractions(commitCallback1);
262         verifyNoMoreInteractions(commitCallback2);
263
264         final FutureCallback<UnsignedLong> commitCallback3 = coordinatedCommit(cohort3);
265         inOrder = inOrder(mockShard);
266         inOrder.verify(mockShard).persistPayload(eq(cohort3.getIdentifier()), any(CommitTransactionPayload.class),
267                 eq(true));
268         inOrder.verify(mockShard).persistPayload(eq(cohort4.getIdentifier()), any(CommitTransactionPayload.class),
269                 eq(false));
270         verifyNoMoreInteractions(commitCallback3);
271         verifyNoMoreInteractions(commitCallback4);
272
273         final ShardDataTreeCohort cohort5 = newShardDataTreeCohort(snapshot ->
274             snapshot.merge(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
275         final FutureCallback<Void> canCommitCallback5 = coordinatedCanCommit(cohort5);
276
277         // The payload instance doesn't matter - it just needs to be of type CommitTransactionPayload.
278         CommitTransactionPayload mockPayload = CommitTransactionPayload.create(nextTransactionId(),
279                 cohort1.getCandidate());
280         shardDataTree.applyReplicatedPayload(cohort1.getIdentifier(), mockPayload);
281         shardDataTree.applyReplicatedPayload(cohort2.getIdentifier(), mockPayload);
282         shardDataTree.applyReplicatedPayload(cohort3.getIdentifier(), mockPayload);
283         shardDataTree.applyReplicatedPayload(cohort4.getIdentifier(), mockPayload);
284
285         inOrder = inOrder(commitCallback1, commitCallback2, commitCallback3, commitCallback4);
286         inOrder.verify(commitCallback1).onSuccess(any(UnsignedLong.class));
287         inOrder.verify(commitCallback2).onSuccess(any(UnsignedLong.class));
288         inOrder.verify(commitCallback3).onSuccess(any(UnsignedLong.class));
289         inOrder.verify(commitCallback4).onSuccess(any(UnsignedLong.class));
290
291         verify(canCommitCallback5).onSuccess(null);
292
293         final DataTreeSnapshot snapshot =
294                 shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
295         Optional<NormalizedNode<?, ?>> optional = snapshot.readNode(carPath);
296         assertEquals("Car node present", true, optional.isPresent());
297         assertEquals("Car node", carNode, optional.get());
298
299         optional = snapshot.readNode(PeopleModel.BASE_PATH);
300         assertEquals("People node present", true, optional.isPresent());
301         assertEquals("People node", peopleNode, optional.get());
302     }
303
304     @Test
305     public void testPipelinedTransactionsWithImmediateCommits() throws Exception {
306         doReturn(false).when(mockShard).canSkipPayload();
307
308         final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
309             snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
310
311         final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
312             snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
313
314         YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
315         MapEntryNode carNode = CarsModel.newCarEntry("optima", new BigInteger("100"));
316         final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot -> snapshot.write(carPath, carNode));
317
318         final FutureCallback<UnsignedLong> commitCallback2 = immediate3PhaseCommit(cohort2);
319         final FutureCallback<UnsignedLong> commitCallback3 = immediate3PhaseCommit(cohort3);
320         final FutureCallback<UnsignedLong> commitCallback1 = immediate3PhaseCommit(cohort1);
321
322         InOrder inOrder = inOrder(mockShard);
323         inOrder.verify(mockShard).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
324                 eq(true));
325         inOrder.verify(mockShard).persistPayload(eq(cohort2.getIdentifier()), any(CommitTransactionPayload.class),
326                 eq(true));
327         inOrder.verify(mockShard).persistPayload(eq(cohort3.getIdentifier()), any(CommitTransactionPayload.class),
328                 eq(false));
329
330         // The payload instance doesn't matter - it just needs to be of type CommitTransactionPayload.
331         CommitTransactionPayload mockPayload = CommitTransactionPayload.create(nextTransactionId(),
332                 cohort1.getCandidate());
333         shardDataTree.applyReplicatedPayload(cohort1.getIdentifier(), mockPayload);
334         shardDataTree.applyReplicatedPayload(cohort2.getIdentifier(), mockPayload);
335         shardDataTree.applyReplicatedPayload(cohort3.getIdentifier(), mockPayload);
336
337         inOrder = inOrder(commitCallback1, commitCallback2, commitCallback3);
338         inOrder.verify(commitCallback1).onSuccess(any(UnsignedLong.class));
339         inOrder.verify(commitCallback2).onSuccess(any(UnsignedLong.class));
340         inOrder.verify(commitCallback3).onSuccess(any(UnsignedLong.class));
341
342         final DataTreeSnapshot snapshot =
343                 shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
344         Optional<NormalizedNode<?, ?>> optional = snapshot.readNode(carPath);
345         assertEquals("Car node present", true, optional.isPresent());
346         assertEquals("Car node", carNode, optional.get());
347     }
348
349     @Test
350     public void testPipelinedTransactionsWithUnmodifiedCandidate() throws Exception {
351         doReturn(false).when(mockShard).canSkipPayload();
352
353         final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
354             snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
355
356         final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
357             snapshot.merge(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
358
359         final FutureCallback<UnsignedLong> commitCallback1 = immediate3PhaseCommit(cohort1);
360
361         verify(mockShard).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class), eq(false));
362
363         final FutureCallback<UnsignedLong> commitCallback2 = immediate3PhaseCommit(cohort2);
364
365         verify(mockShard, never()).persistPayload(eq(cohort2.getIdentifier()), any(CommitTransactionPayload.class),
366                 anyBoolean());
367
368         // The payload instance doesn't matter - it just needs to be of type CommitTransactionPayload.
369         shardDataTree.applyReplicatedPayload(cohort1.getIdentifier(),
370                 CommitTransactionPayload.create(nextTransactionId(), cohort1.getCandidate()));
371
372         InOrder inOrder = inOrder(commitCallback1, commitCallback2);
373         inOrder.verify(commitCallback1).onSuccess(any(UnsignedLong.class));
374         inOrder.verify(commitCallback2).onSuccess(any(UnsignedLong.class));
375
376         final DataTreeSnapshot snapshot =
377                 shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
378         Optional<NormalizedNode<?, ?>> optional = snapshot.readNode(CarsModel.BASE_PATH);
379         assertEquals("Car node present", true, optional.isPresent());
380     }
381
382     @SuppressWarnings("unchecked")
383     @Test
384     public void testAbortWithPendingCommits() throws Exception {
385         doReturn(false).when(mockShard).canSkipPayload();
386
387         final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
388             snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
389
390         final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
391             snapshot.write(PeopleModel.BASE_PATH, PeopleModel.create()));
392
393         final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot ->
394             snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
395
396         YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
397         MapEntryNode carNode = CarsModel.newCarEntry("optima", new BigInteger("100"));
398         final ShardDataTreeCohort cohort4 = newShardDataTreeCohort(snapshot -> snapshot.write(carPath, carNode));
399
400         coordinatedCanCommit(cohort2);
401         immediateCanCommit(cohort1);
402         coordinatedCanCommit(cohort3);
403         coordinatedCanCommit(cohort4);
404
405         coordinatedPreCommit(cohort1);
406         coordinatedPreCommit(cohort2);
407         coordinatedPreCommit(cohort3);
408
409         FutureCallback<Void> mockAbortCallback = mock(FutureCallback.class);
410         doNothing().when(mockAbortCallback).onSuccess(null);
411         cohort2.abort(mockAbortCallback);
412         verify(mockAbortCallback).onSuccess(null);
413
414         coordinatedPreCommit(cohort4);
415         coordinatedCommit(cohort1);
416         coordinatedCommit(cohort3);
417         coordinatedCommit(cohort4);
418
419         InOrder inOrder = inOrder(mockShard);
420         inOrder.verify(mockShard).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
421                 eq(false));
422         inOrder.verify(mockShard).persistPayload(eq(cohort3.getIdentifier()), any(CommitTransactionPayload.class),
423                 eq(false));
424         inOrder.verify(mockShard).persistPayload(eq(cohort4.getIdentifier()), any(CommitTransactionPayload.class),
425                 eq(false));
426
427         // The payload instance doesn't matter - it just needs to be of type CommitTransactionPayload.
428         CommitTransactionPayload mockPayload = CommitTransactionPayload.create(nextTransactionId(),
429                 cohort1.getCandidate());
430         shardDataTree.applyReplicatedPayload(cohort1.getIdentifier(), mockPayload);
431         shardDataTree.applyReplicatedPayload(cohort3.getIdentifier(), mockPayload);
432         shardDataTree.applyReplicatedPayload(cohort4.getIdentifier(), mockPayload);
433
434         final DataTreeSnapshot snapshot =
435                 shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
436         Optional<NormalizedNode<?, ?>> optional = snapshot.readNode(carPath);
437         assertEquals("Car node present", true, optional.isPresent());
438         assertEquals("Car node", carNode, optional.get());
439     }
440
441     @SuppressWarnings("unchecked")
442     @Test
443     public void testAbortWithFailedRebase() throws Exception {
444         final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
445             snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
446
447         final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
448             snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
449
450         NormalizedNode<?, ?> peopleNode = PeopleModel.create();
451         final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot ->
452             snapshot.write(PeopleModel.BASE_PATH, peopleNode));
453
454         immediateCanCommit(cohort1);
455         FutureCallback<Void> canCommitCallback2 = coordinatedCanCommit(cohort2);
456
457         coordinatedPreCommit(cohort1);
458         verify(canCommitCallback2).onSuccess(null);
459
460         FutureCallback<Void> mockAbortCallback = mock(FutureCallback.class);
461         doNothing().when(mockAbortCallback).onSuccess(null);
462         cohort1.abort(mockAbortCallback);
463         verify(mockAbortCallback).onSuccess(null);
464
465         FutureCallback<DataTreeCandidate> preCommitCallback2 = coordinatedPreCommit(cohort2);
466         verify(preCommitCallback2).onFailure(any(Throwable.class));
467
468         immediateCanCommit(cohort3);
469         immediatePreCommit(cohort3);
470         immediateCommit(cohort3);
471
472         final DataTreeSnapshot snapshot =
473                 shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
474         Optional<NormalizedNode<?, ?>> optional = snapshot.readNode(PeopleModel.BASE_PATH);
475         assertEquals("People node present", true, optional.isPresent());
476         assertEquals("People node", peopleNode, optional.get());
477     }
478
479     private ShardDataTreeCohort newShardDataTreeCohort(final DataTreeOperation operation) {
480         final ReadWriteShardDataTreeTransaction transaction =
481                 shardDataTree.newReadWriteTransaction(nextTransactionId());
482         final DataTreeModification snapshot = transaction.getSnapshot();
483         operation.execute(snapshot);
484         return shardDataTree.finishTransaction(transaction);
485     }
486
487     @SuppressWarnings({ "rawtypes", "unchecked" })
488     private static void verifyOnDataTreeChanged(final DOMDataTreeChangeListener listener,
489             final Consumer<DataTreeCandidate> callback) {
490         ArgumentCaptor<Collection> changes = ArgumentCaptor.forClass(Collection.class);
491         verify(listener, atLeastOnce()).onDataTreeChanged(changes.capture());
492         for (Collection list : changes.getAllValues()) {
493             for (Object dtc : list) {
494                 callback.accept((DataTreeCandidate)dtc);
495             }
496         }
497
498         reset(listener);
499     }
500
501     private static NormalizedNode<?, ?> getCars(final ShardDataTree shardDataTree) {
502         final ReadOnlyShardDataTreeTransaction readOnlyShardDataTreeTransaction =
503                 shardDataTree.newReadOnlyTransaction(nextTransactionId());
504         final DataTreeSnapshot snapshot1 = readOnlyShardDataTreeTransaction.getSnapshot();
505
506         final Optional<NormalizedNode<?, ?>> optional = snapshot1.readNode(CarsModel.BASE_PATH);
507
508         assertEquals(true, optional.isPresent());
509
510         return optional.get();
511     }
512
513     private static DataTreeCandidate addCar(final ShardDataTree shardDataTree)
514             throws ExecutionException, InterruptedException {
515         return addCar(shardDataTree, "altima");
516     }
517
518     private static DataTreeCandidate addCar(final ShardDataTree shardDataTree, final String name)
519             throws ExecutionException, InterruptedException {
520         return doTransaction(shardDataTree, snapshot -> {
521             snapshot.merge(CarsModel.BASE_PATH, CarsModel.emptyContainer());
522             snapshot.merge(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode());
523             snapshot.write(CarsModel.newCarPath(name), CarsModel.newCarEntry(name, new BigInteger("100")));
524         });
525     }
526
527     private static DataTreeCandidate removeCar(final ShardDataTree shardDataTree)
528             throws ExecutionException, InterruptedException {
529         return doTransaction(shardDataTree, snapshot -> snapshot.delete(CarsModel.newCarPath("altima")));
530     }
531
532     @FunctionalInterface
533     private interface DataTreeOperation {
534         void execute(DataTreeModification snapshot);
535     }
536
537     private static DataTreeCandidate doTransaction(final ShardDataTree shardDataTree,
538             final DataTreeOperation operation) throws ExecutionException, InterruptedException {
539         final ReadWriteShardDataTreeTransaction transaction =
540                 shardDataTree.newReadWriteTransaction(nextTransactionId());
541         final DataTreeModification snapshot = transaction.getSnapshot();
542         operation.execute(snapshot);
543         final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
544
545         immediateCanCommit(cohort);
546         immediatePreCommit(cohort);
547         final DataTreeCandidate candidate = cohort.getCandidate();
548         immediateCommit(cohort);
549
550         return candidate;
551     }
552
553     private static DataTreeCandidate applyCandidates(final ShardDataTree shardDataTree,
554             final List<DataTreeCandidate> candidates) throws ExecutionException, InterruptedException {
555         final ReadWriteShardDataTreeTransaction transaction =
556                 shardDataTree.newReadWriteTransaction(nextTransactionId());
557         final DataTreeModification snapshot = transaction.getSnapshot();
558         for (final DataTreeCandidate candidateTip : candidates) {
559             DataTreeCandidates.applyToModification(snapshot, candidateTip);
560         }
561         final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
562
563         immediateCanCommit(cohort);
564         immediatePreCommit(cohort);
565         final DataTreeCandidate candidate = cohort.getCandidate();
566         immediateCommit(cohort);
567
568         return candidate;
569     }
570 }