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