2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.cluster.datastore;
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;
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;
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;
53 public class ShardDataTreeTest extends AbstractTest {
55 private final Shard mockShard = Mockito.mock(Shard.class);
58 private SchemaContext fullSchema;
62 doReturn(true).when(mockShard).canSkipPayload();
63 doReturn(Ticker.systemTicker()).when(mockShard).ticker();
64 doReturn(Mockito.mock(ShardStats.class)).when(mockShard).getShardMBean();
66 fullSchema = SchemaContextHelper.full();
70 public void testWrite() throws ExecutionException, InterruptedException {
71 modify(new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL), false, true, true);
75 public void testMerge() throws ExecutionException, InterruptedException {
76 modify(new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL), true, true, true);
80 private void modify(final ShardDataTree shardDataTree, final boolean merge, final boolean expectedCarsPresent,
81 final boolean expectedPeoplePresent) throws ExecutionException, InterruptedException {
83 assertEquals(fullSchema, shardDataTree.getSchemaContext());
85 final ReadWriteShardDataTreeTransaction transaction =
86 shardDataTree.newReadWriteTransaction(nextTransactionId());
88 final DataTreeModification snapshot = transaction.getSnapshot();
90 assertNotNull(snapshot);
93 snapshot.merge(CarsModel.BASE_PATH, CarsModel.create());
94 snapshot.merge(PeopleModel.BASE_PATH, PeopleModel.create());
96 snapshot.write(CarsModel.BASE_PATH, CarsModel.create());
97 snapshot.write(PeopleModel.BASE_PATH, PeopleModel.create());
100 final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
102 immediateCanCommit(cohort);
103 immediatePreCommit(cohort);
104 immediateCommit(cohort);
106 final ReadOnlyShardDataTreeTransaction readOnlyShardDataTreeTransaction =
107 shardDataTree.newReadOnlyTransaction(nextTransactionId());
109 final DataTreeSnapshot snapshot1 = readOnlyShardDataTreeTransaction.getSnapshot();
111 final Optional<NormalizedNode<?, ?>> optional = snapshot1.readNode(CarsModel.BASE_PATH);
113 assertEquals(expectedCarsPresent, optional.isPresent());
115 final Optional<NormalizedNode<?, ?>> optional1 = snapshot1.readNode(PeopleModel.BASE_PATH);
117 assertEquals(expectedPeoplePresent, optional1.isPresent());
122 public void bug4359AddRemoveCarOnce() throws ExecutionException, InterruptedException {
123 final ShardDataTree shardDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
125 final List<DataTreeCandidateTip> candidates = new ArrayList<>();
126 candidates.add(addCar(shardDataTree));
127 candidates.add(removeCar(shardDataTree));
129 final NormalizedNode<?, ?> expected = getCars(shardDataTree);
131 applyCandidates(shardDataTree, candidates);
133 final NormalizedNode<?, ?> actual = getCars(shardDataTree);
135 assertEquals(expected, actual);
139 public void bug4359AddRemoveCarTwice() throws ExecutionException, InterruptedException {
140 final ShardDataTree shardDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
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));
148 final NormalizedNode<?, ?> expected = getCars(shardDataTree);
150 applyCandidates(shardDataTree, candidates);
152 final NormalizedNode<?, ?> actual = getCars(shardDataTree);
154 assertEquals(expected, actual);
158 public void testListenerNotifiedOnApplySnapshot() throws Exception {
159 final ShardDataTree shardDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
161 DOMDataTreeChangeListener listener = mock(DOMDataTreeChangeListener.class);
162 shardDataTree.registerTreeChangeListener(CarsModel.CAR_LIST_PATH.node(CarsModel.CAR_QNAME), listener);
164 addCar(shardDataTree, "optima");
166 verifyOnDataTreeChanged(listener, dtc -> {
167 assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
168 assertEquals("getRootPath", CarsModel.newCarPath("optima"), dtc.getRootPath());
171 addCar(shardDataTree, "sportage");
173 verifyOnDataTreeChanged(listener, dtc -> {
174 assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
175 assertEquals("getRootPath", CarsModel.newCarPath("sportage"), dtc.getRootPath());
178 ShardDataTree newDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
179 addCar(newDataTree, "optima");
180 addCar(newDataTree, "murano");
182 shardDataTree.applySnapshot(newDataTree.takeStateSnapshot());
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());
194 if (!expChanges.isEmpty()) {
195 fail("Missing change notifications: " + expChanges);
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);
213 private static NormalizedNode<?, ?> getCars(final ShardDataTree shardDataTree) {
214 final ReadOnlyShardDataTreeTransaction readOnlyShardDataTreeTransaction =
215 shardDataTree.newReadOnlyTransaction(nextTransactionId());
216 final DataTreeSnapshot snapshot1 = readOnlyShardDataTreeTransaction.getSnapshot();
218 final Optional<NormalizedNode<?, ?>> optional = snapshot1.readNode(CarsModel.BASE_PATH);
220 assertEquals(true, optional.isPresent());
222 return optional.get();
225 private static DataTreeCandidateTip addCar(final ShardDataTree shardDataTree)
226 throws ExecutionException, InterruptedException {
227 return addCar(shardDataTree, "altima");
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")));
239 private static DataTreeCandidateTip removeCar(final ShardDataTree shardDataTree)
240 throws ExecutionException, InterruptedException {
241 return doTransaction(shardDataTree, snapshot -> snapshot.delete(CarsModel.newCarPath("altima")));
245 private interface DataTreeOperation {
246 void execute(DataTreeModification snapshot);
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);
257 immediateCanCommit(cohort);
258 immediatePreCommit(cohort);
259 final DataTreeCandidateTip candidate = cohort.getCandidate();
260 immediateCommit(cohort);
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);
273 final ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction);
275 immediateCanCommit(cohort);
276 immediatePreCommit(cohort);
277 final DataTreeCandidateTip candidate = cohort.getCandidate();
278 immediateCommit(cohort);