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
8 package org.opendaylight.mdsal.dom.broker;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertNotNull;
12 import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
13 import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;
15 import com.google.common.collect.ImmutableMap;
16 import com.google.common.util.concurrent.ForwardingExecutorService;
17 import com.google.common.util.concurrent.ListeningExecutorService;
18 import com.google.common.util.concurrent.MoreExecutors;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.concurrent.CountDownLatch;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.ExecutorService;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.TimeUnit;
26 import org.junit.After;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
30 import org.opendaylight.mdsal.common.api.TransactionCommitDeadlockException;
31 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
32 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
33 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
34 import org.opendaylight.mdsal.dom.spi.store.DOMStore;
35 import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataStore;
36 import org.opendaylight.yangtools.util.concurrent.DeadlockDetectingListeningExecutorService;
37 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
40 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
42 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
43 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
44 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
45 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
46 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
48 public class DOMDataTreeListenerTest extends AbstractDatastoreTest {
50 private AbstractDOMDataBroker domBroker;
51 private ListeningExecutorService executor;
52 private ExecutorService futureExecutor;
53 private CommitExecutorService commitExecutor;
55 private static final MapNode OUTER_LIST = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
56 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1))
59 private static final MapNode OUTER_LIST_2 = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
60 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2))
63 private static final NormalizedNode TEST_CONTAINER = Builders.containerBuilder()
64 .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
65 .withChild(OUTER_LIST)
68 private static final NormalizedNode TEST_CONTAINER_2 = Builders.containerBuilder()
69 .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
70 .withChild(OUTER_LIST_2)
73 private static final DOMDataTreeIdentifier ROOT_DATA_TREE_ID =
74 DOMDataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
76 private static final DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID =
77 DOMDataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
80 public void setupStore() {
81 final InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER",
82 MoreExecutors.newDirectExecutorService());
83 final InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG",
84 MoreExecutors.newDirectExecutorService());
86 operStore.onModelContextUpdated(SCHEMA_CONTEXT);
87 configStore.onModelContextUpdated(SCHEMA_CONTEXT);
89 final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType,
91 .put(CONFIGURATION, configStore)
92 .put(OPERATIONAL, operStore)
95 commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
96 futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB",
97 DOMDataTreeListenerTest.class);
98 executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
99 TransactionCommitDeadlockException.DEADLOCK_EXCEPTION_SUPPLIER, futureExecutor);
100 domBroker = new SerializedDOMDataBroker(stores, executor);
104 public void tearDown() {
105 if (executor != null) {
106 executor.shutdownNow();
109 if (futureExecutor != null) {
110 futureExecutor.shutdownNow();
115 public void writeContainerEmptyTreeTest() throws InterruptedException {
116 final var latch = new CountDownLatch(1);
118 final var dataTreeChangeService = getDOMDataTreeChangeService();
119 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
120 dataTreeChangeService);
122 final var listener = new TestDataTreeListener(latch);
123 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
125 final var writeTx = domBroker.newWriteOnlyTransaction();
126 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
129 latch.await(5, TimeUnit.SECONDS);
131 assertEquals(1, listener.getReceivedChanges().size());
132 final var changes = listener.getReceivedChanges().get(0);
133 assertEquals(1, changes.size());
135 final var candidate = changes.get(0);
136 assertNotNull(candidate);
137 final var candidateRoot = candidate.getRootNode();
138 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
143 public void replaceContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
144 final var latch = new CountDownLatch(2);
146 final var dataTreeChangeService = getDOMDataTreeChangeService();
147 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
148 dataTreeChangeService);
150 var writeTx = domBroker.newWriteOnlyTransaction();
151 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
152 writeTx.commit().get();
154 final var listener = new TestDataTreeListener(latch);
155 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
156 writeTx = domBroker.newWriteOnlyTransaction();
157 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
160 latch.await(5, TimeUnit.SECONDS);
162 assertEquals(2, listener.getReceivedChanges().size());
163 var changes = listener.getReceivedChanges().get(0);
164 assertEquals(1, changes.size());
166 var candidate = changes.get(0);
167 assertNotNull(candidate);
168 var candidateRoot = candidate.getRootNode();
169 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
171 changes = listener.getReceivedChanges().get(1);
172 assertEquals(1, changes.size());
174 candidate = changes.get(0);
175 assertNotNull(candidate);
176 candidateRoot = candidate.getRootNode();
177 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
182 public void deleteContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
183 final var latch = new CountDownLatch(2);
185 final var dataTreeChangeService = getDOMDataTreeChangeService();
186 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
188 var writeTx = domBroker.newWriteOnlyTransaction();
189 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
190 writeTx.commit().get();
192 final var listener = new TestDataTreeListener(latch);
193 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
195 writeTx = domBroker.newWriteOnlyTransaction();
196 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
199 latch.await(5, TimeUnit.SECONDS);
201 assertEquals(2, listener.getReceivedChanges().size());
202 var changes = listener.getReceivedChanges().get(0);
203 assertEquals(1, changes.size());
205 var candidate = changes.get(0);
206 assertNotNull(candidate);
207 var candidateRoot = candidate.getRootNode();
208 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
210 changes = listener.getReceivedChanges().get(1);
211 assertEquals(1, changes.size());
213 candidate = changes.get(0);
214 assertNotNull(candidate);
215 candidateRoot = candidate.getRootNode();
216 checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
221 public void replaceChildListContainerInTreeTest() throws ExecutionException, InterruptedException {
222 final var latch = new CountDownLatch(2);
224 final var dataTreeChangeService = getDOMDataTreeChangeService();
225 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
227 var writeTx = domBroker.newWriteOnlyTransaction();
228 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
229 writeTx.commit().get();
231 final var listener = new TestDataTreeListener(latch);
232 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
234 writeTx = domBroker.newWriteOnlyTransaction();
235 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
238 latch.await(5, TimeUnit.SECONDS);
240 assertEquals(2, listener.getReceivedChanges().size());
241 var changes = listener.getReceivedChanges().get(0);
242 assertEquals(1, changes.size());
244 var candidate = changes.get(0);
245 assertNotNull(candidate);
246 var candidateRoot = candidate.getRootNode();
247 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
249 changes = listener.getReceivedChanges().get(1);
250 assertEquals(1, changes.size());
252 candidate = changes.get(0);
253 assertNotNull(candidate);
254 candidateRoot = candidate.getRootNode();
255 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
256 final var modifiedChild = candidateRoot.getModifiedChild(new NodeIdentifier(TestModel.OUTER_LIST_QNAME));
257 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
262 public void rootModificationChildListenerTest() throws ExecutionException, InterruptedException {
263 final var latch = new CountDownLatch(2);
265 final var dataTreeChangeService = getDOMDataTreeChangeService();
266 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
267 dataTreeChangeService);
269 var writeTx = domBroker.newWriteOnlyTransaction();
270 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
271 writeTx.commit().get();
273 final var listener = new TestDataTreeListener(latch);
274 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
276 writeTx = domBroker.newWriteOnlyTransaction();
277 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
278 writeTx.commit().get();
280 latch.await(1, TimeUnit.SECONDS);
282 assertEquals(2, listener.getReceivedChanges().size());
283 var changes = listener.getReceivedChanges().get(0);
284 assertEquals(1, changes.size());
286 var candidate = changes.get(0);
287 assertNotNull(candidate);
288 var candidateRoot = candidate.getRootNode();
289 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
291 changes = listener.getReceivedChanges().get(1);
292 assertEquals(1, changes.size());
294 candidate = changes.get(0);
295 assertNotNull(candidate);
296 candidateRoot = candidate.getRootNode();
297 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
302 public void listEntryChangeNonRootRegistrationTest() throws ExecutionException, InterruptedException {
303 final var latch = new CountDownLatch(2);
305 final var dataTreeChangeService = getDOMDataTreeChangeService();
306 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
308 var writeTx = domBroker.newWriteOnlyTransaction();
309 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
310 writeTx.commit().get();
312 final var listener = new TestDataTreeListener(latch);
313 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
315 final var outerListEntryId1 =
316 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
317 final var outerListEntryId2 =
318 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
319 final var outerListEntryId3 =
320 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
322 final var outerListEntry1 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
323 final var outerListEntry2 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
324 final var outerListEntry3 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
326 final var listAfter = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
327 .withChild(outerListEntry2)
328 .withChild(outerListEntry3)
331 writeTx = domBroker.newWriteOnlyTransaction();
332 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
333 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
335 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
339 latch.await(5, TimeUnit.SECONDS);
341 assertEquals(2, listener.getReceivedChanges().size());
342 var changes = listener.getReceivedChanges().get(0);
343 assertEquals(1, changes.size());
345 var candidate = changes.get(0);
346 assertNotNull(candidate);
347 var candidateRoot = candidate.getRootNode();
348 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
350 changes = listener.getReceivedChanges().get(1);
351 assertEquals(1, changes.size());
353 candidate = changes.get(0);
354 assertNotNull(candidate);
355 candidateRoot = candidate.getRootNode();
356 checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
357 final var entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1);
358 checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
359 final var entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2);
360 checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
361 final var entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3);
362 checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
366 private static void checkChange(final NormalizedNode expectedBefore, final NormalizedNode expectedAfter,
367 final ModificationType expectedMod, final DataTreeCandidateNode candidateNode) {
368 assertEquals(expectedBefore, candidateNode.dataBefore());
369 assertEquals(expectedAfter, candidateNode.dataAfter());
370 assertEquals(expectedMod, candidateNode.modificationType());
373 private DOMDataTreeChangeService getDOMDataTreeChangeService() {
374 return domBroker.extension(DOMDataTreeChangeService.class);
377 static class CommitExecutorService extends ForwardingExecutorService {
379 ExecutorService delegate;
381 CommitExecutorService(final ExecutorService delegate) {
382 this.delegate = delegate;
386 protected ExecutorService delegate() {
391 static class TestDataTreeListener implements DOMDataTreeChangeListener {
392 private final List<List<DataTreeCandidate>> receivedChanges = new ArrayList<>();
393 private final CountDownLatch latch;
395 TestDataTreeListener(final CountDownLatch latch) {
400 public void onDataTreeChanged(final List<DataTreeCandidate> changes) {
401 receivedChanges.add(changes);
406 public void onInitialData() {
410 List<List<DataTreeCandidate>> getReceivedChanges() {
411 return receivedChanges;