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