c38254769043d96d0902cfb27d1d7f4de2e15514
[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.Mockito.atLeastOnce;
15 import static org.mockito.Mockito.doReturn;
16 import static org.mockito.Mockito.mock;
17 import static org.mockito.Mockito.reset;
18 import static org.mockito.Mockito.verify;
19 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.immediateCanCommit;
20 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.immediateCommit;
21 import static org.opendaylight.controller.cluster.datastore.ShardDataTreeMocking.immediatePreCommit;
22
23 import com.google.common.base.Optional;
24 import com.google.common.base.Ticker;
25 import com.google.common.collect.Maps;
26 import java.math.BigInteger;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.concurrent.ExecutionException;
32 import java.util.function.Consumer;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.mockito.ArgumentCaptor;
36 import org.mockito.Mockito;
37 import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardStats;
38 import org.opendaylight.controller.md.cluster.datastore.model.CarsModel;
39 import org.opendaylight.controller.md.cluster.datastore.model.PeopleModel;
40 import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper;
41 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
45 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
46 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
47 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
48 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
49 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
50 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
51
52 public class ShardDataTreeTest extends AbstractTest {
53
54     private final Shard mockShard = Mockito.mock(Shard.class);
55
56
57     private SchemaContext fullSchema;
58
59     @Before
60     public void setUp() {
61         doReturn(true).when(mockShard).canSkipPayload();
62         doReturn(Ticker.systemTicker()).when(mockShard).ticker();
63         doReturn(Mockito.mock(ShardStats.class)).when(mockShard).getShardMBean();
64
65         fullSchema = SchemaContextHelper.full();
66     }
67
68     @Test
69     public void testWrite() throws ExecutionException, InterruptedException {
70         modify(new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL), false, true, true);
71     }
72
73     @Test
74     public void testMerge() throws ExecutionException, InterruptedException {
75         modify(new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL), true, true, true);
76     }
77
78
79     private void modify(final ShardDataTree shardDataTree, final boolean merge, final boolean expectedCarsPresent,
80             final boolean expectedPeoplePresent) throws ExecutionException, InterruptedException {
81
82         assertEquals(fullSchema, shardDataTree.getSchemaContext());
83
84         final ReadWriteShardDataTreeTransaction transaction =
85                 shardDataTree.newReadWriteTransaction(nextTransactionId());
86
87         final DataTreeModification snapshot = transaction.getSnapshot();
88
89         assertNotNull(snapshot);
90
91         if (merge) {
92             snapshot.merge(CarsModel.BASE_PATH, CarsModel.create());
93             snapshot.merge(PeopleModel.BASE_PATH, PeopleModel.create());
94         } else {
95             snapshot.write(CarsModel.BASE_PATH, CarsModel.create());
96             snapshot.write(PeopleModel.BASE_PATH, PeopleModel.create());
97         }
98
99         final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
100
101         immediateCanCommit(cohort);
102         immediatePreCommit(cohort);
103         immediateCommit(cohort);
104
105         final ReadOnlyShardDataTreeTransaction readOnlyShardDataTreeTransaction =
106                 shardDataTree.newReadOnlyTransaction(nextTransactionId());
107
108         final DataTreeSnapshot snapshot1 = readOnlyShardDataTreeTransaction.getSnapshot();
109
110         final Optional<NormalizedNode<?, ?>> optional = snapshot1.readNode(CarsModel.BASE_PATH);
111
112         assertEquals(expectedCarsPresent, optional.isPresent());
113
114         final Optional<NormalizedNode<?, ?>> optional1 = snapshot1.readNode(PeopleModel.BASE_PATH);
115
116         assertEquals(expectedPeoplePresent, optional1.isPresent());
117
118     }
119
120     @Test
121     public void bug4359AddRemoveCarOnce() throws ExecutionException, InterruptedException {
122         final ShardDataTree shardDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
123
124         final List<DataTreeCandidate> candidates = new ArrayList<>();
125         candidates.add(addCar(shardDataTree));
126         candidates.add(removeCar(shardDataTree));
127
128         final NormalizedNode<?, ?> expected = getCars(shardDataTree);
129
130         applyCandidates(shardDataTree, candidates);
131
132         final NormalizedNode<?, ?> actual = getCars(shardDataTree);
133
134         assertEquals(expected, actual);
135     }
136
137     @Test
138     public void bug4359AddRemoveCarTwice() throws ExecutionException, InterruptedException {
139         final ShardDataTree shardDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
140
141         final List<DataTreeCandidate> candidates = new ArrayList<>();
142         candidates.add(addCar(shardDataTree));
143         candidates.add(removeCar(shardDataTree));
144         candidates.add(addCar(shardDataTree));
145         candidates.add(removeCar(shardDataTree));
146
147         final NormalizedNode<?, ?> expected = getCars(shardDataTree);
148
149         applyCandidates(shardDataTree, candidates);
150
151         final NormalizedNode<?, ?> actual = getCars(shardDataTree);
152
153         assertEquals(expected, actual);
154     }
155
156     @Test
157     public void testListenerNotifiedOnApplySnapshot() throws Exception {
158         final ShardDataTree shardDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
159
160         DOMDataTreeChangeListener listener = mock(DOMDataTreeChangeListener.class);
161         shardDataTree.registerTreeChangeListener(CarsModel.CAR_LIST_PATH.node(CarsModel.CAR_QNAME), listener);
162
163         addCar(shardDataTree, "optima");
164
165         verifyOnDataTreeChanged(listener, dtc -> {
166             assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
167             assertEquals("getRootPath", CarsModel.newCarPath("optima"), dtc.getRootPath());
168         });
169
170         addCar(shardDataTree, "sportage");
171
172         verifyOnDataTreeChanged(listener, dtc -> {
173             assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
174             assertEquals("getRootPath", CarsModel.newCarPath("sportage"), dtc.getRootPath());
175         });
176
177         ShardDataTree newDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
178         addCar(newDataTree, "optima");
179         addCar(newDataTree, "murano");
180
181         shardDataTree.applySnapshot(newDataTree.takeStateSnapshot());
182
183         Map<YangInstanceIdentifier, ModificationType> expChanges = Maps.newHashMap();
184         expChanges.put(CarsModel.newCarPath("optima"), ModificationType.WRITE);
185         expChanges.put(CarsModel.newCarPath("murano"), ModificationType.WRITE);
186         expChanges.put(CarsModel.newCarPath("sportage"), ModificationType.DELETE);
187         verifyOnDataTreeChanged(listener, dtc -> {
188             ModificationType expType = expChanges.remove(dtc.getRootPath());
189             assertNotNull("Got unexpected change for " + dtc.getRootPath(), expType);
190             assertEquals("getModificationType", expType, dtc.getRootNode().getModificationType());
191         });
192
193         if (!expChanges.isEmpty()) {
194             fail("Missing change notifications: " + expChanges);
195         }
196     }
197
198     @SuppressWarnings({ "rawtypes", "unchecked" })
199     private static void verifyOnDataTreeChanged(final DOMDataTreeChangeListener listener,
200             final Consumer<DataTreeCandidate> callback) {
201         ArgumentCaptor<Collection> changes = ArgumentCaptor.forClass(Collection.class);
202         verify(listener, atLeastOnce()).onDataTreeChanged(changes.capture());
203         for (Collection list : changes.getAllValues()) {
204             for (Object dtc : list) {
205                 callback.accept((DataTreeCandidate)dtc);
206             }
207         }
208
209         reset(listener);
210     }
211
212     private static NormalizedNode<?, ?> getCars(final ShardDataTree shardDataTree) {
213         final ReadOnlyShardDataTreeTransaction readOnlyShardDataTreeTransaction =
214                 shardDataTree.newReadOnlyTransaction(nextTransactionId());
215         final DataTreeSnapshot snapshot1 = readOnlyShardDataTreeTransaction.getSnapshot();
216
217         final Optional<NormalizedNode<?, ?>> optional = snapshot1.readNode(CarsModel.BASE_PATH);
218
219         assertEquals(true, optional.isPresent());
220
221         return optional.get();
222     }
223
224     private static DataTreeCandidate addCar(final ShardDataTree shardDataTree)
225             throws ExecutionException, InterruptedException {
226         return addCar(shardDataTree, "altima");
227     }
228
229     private static DataTreeCandidate addCar(final ShardDataTree shardDataTree, final String name)
230             throws ExecutionException, InterruptedException {
231         return doTransaction(shardDataTree, snapshot -> {
232             snapshot.merge(CarsModel.BASE_PATH, CarsModel.emptyContainer());
233             snapshot.merge(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode());
234             snapshot.write(CarsModel.newCarPath(name), CarsModel.newCarEntry(name, new BigInteger("100")));
235         });
236     }
237
238     private static DataTreeCandidate removeCar(final ShardDataTree shardDataTree)
239             throws ExecutionException, InterruptedException {
240         return doTransaction(shardDataTree, snapshot -> snapshot.delete(CarsModel.newCarPath("altima")));
241     }
242
243     @FunctionalInterface
244     private interface DataTreeOperation {
245         void execute(DataTreeModification snapshot);
246     }
247
248     private static DataTreeCandidate doTransaction(final ShardDataTree shardDataTree,
249             final DataTreeOperation operation) throws ExecutionException, InterruptedException {
250         final ReadWriteShardDataTreeTransaction transaction =
251                 shardDataTree.newReadWriteTransaction(nextTransactionId());
252         final DataTreeModification snapshot = transaction.getSnapshot();
253         operation.execute(snapshot);
254         final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
255
256         immediateCanCommit(cohort);
257         immediatePreCommit(cohort);
258         final DataTreeCandidate candidate = cohort.getCandidate();
259         immediateCommit(cohort);
260
261         return candidate;
262     }
263
264     private static DataTreeCandidate applyCandidates(final ShardDataTree shardDataTree,
265             final List<DataTreeCandidate> candidates) throws ExecutionException, InterruptedException {
266         final ReadWriteShardDataTreeTransaction transaction =
267                 shardDataTree.newReadWriteTransaction(nextTransactionId());
268         final DataTreeModification snapshot = transaction.getSnapshot();
269         for (final DataTreeCandidate candidateTip : candidates) {
270             DataTreeCandidates.applyToModification(snapshot, candidateTip);
271         }
272         final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
273
274         immediateCanCommit(cohort);
275         immediatePreCommit(cohort);
276         final DataTreeCandidate candidate = cohort.getCandidate();
277         immediateCommit(cohort);
278
279         return candidate;
280     }
281 }