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