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.AbstractDOMDataBroker;
35 import org.opendaylight.mdsal.dom.spi.store.DOMStore;
36 import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataStore;
37 import org.opendaylight.yangtools.util.concurrent.DeadlockDetectingListeningExecutorService;
38 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
41 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
43 import org.opendaylight.yangtools.yang.data.spi.node.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.newSystemMapBuilder()
56 .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
57 .withChild(TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1))
60 private static final MapNode OUTER_LIST_2 = ImmutableNodes.newSystemMapBuilder()
61 .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
62 .withChild(TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2))
65 private static final NormalizedNode TEST_CONTAINER = ImmutableNodes.newContainerBuilder()
66 .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
67 .withChild(OUTER_LIST)
70 private static final NormalizedNode TEST_CONTAINER_2 = ImmutableNodes.newContainerBuilder()
71 .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
72 .withChild(OUTER_LIST_2)
75 private static final DOMDataTreeIdentifier ROOT_DATA_TREE_ID =
76 DOMDataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
78 private static final DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID =
79 DOMDataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
82 public void setupStore() {
83 final InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER",
84 MoreExecutors.newDirectExecutorService());
85 final InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG",
86 MoreExecutors.newDirectExecutorService());
88 operStore.onModelContextUpdated(SCHEMA_CONTEXT);
89 configStore.onModelContextUpdated(SCHEMA_CONTEXT);
91 final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType,
93 .put(CONFIGURATION, configStore)
94 .put(OPERATIONAL, operStore)
97 commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
98 futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB",
99 DOMDataTreeListenerTest.class);
100 executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
101 TransactionCommitDeadlockException.DEADLOCK_EXCEPTION_SUPPLIER, futureExecutor);
102 domBroker = new SerializedDOMDataBroker(stores, executor);
106 public void tearDown() {
107 if (executor != null) {
108 executor.shutdownNow();
111 if (futureExecutor != null) {
112 futureExecutor.shutdownNow();
117 public void writeContainerEmptyTreeTest() throws InterruptedException {
118 final var latch = new CountDownLatch(1);
120 final var dataTreeChangeService = getDOMDataTreeChangeService();
121 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
122 dataTreeChangeService);
124 final var listener = new TestDataTreeListener(latch);
125 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
127 final var writeTx = domBroker.newWriteOnlyTransaction();
128 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
131 latch.await(5, TimeUnit.SECONDS);
133 assertEquals(1, listener.getReceivedChanges().size());
134 final var changes = listener.getReceivedChanges().get(0);
135 assertEquals(1, changes.size());
137 final var candidate = changes.get(0);
138 assertNotNull(candidate);
139 final var candidateRoot = candidate.getRootNode();
140 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
145 public void replaceContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
146 final var latch = new CountDownLatch(2);
148 final var dataTreeChangeService = getDOMDataTreeChangeService();
149 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
150 dataTreeChangeService);
152 var writeTx = domBroker.newWriteOnlyTransaction();
153 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
154 writeTx.commit().get();
156 final var listener = new TestDataTreeListener(latch);
157 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
158 writeTx = domBroker.newWriteOnlyTransaction();
159 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
162 latch.await(5, TimeUnit.SECONDS);
164 assertEquals(2, listener.getReceivedChanges().size());
165 var changes = listener.getReceivedChanges().get(0);
166 assertEquals(1, changes.size());
168 var candidate = changes.get(0);
169 assertNotNull(candidate);
170 var candidateRoot = candidate.getRootNode();
171 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
173 changes = listener.getReceivedChanges().get(1);
174 assertEquals(1, changes.size());
176 candidate = changes.get(0);
177 assertNotNull(candidate);
178 candidateRoot = candidate.getRootNode();
179 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
184 public void deleteContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
185 final var latch = new CountDownLatch(2);
187 final var dataTreeChangeService = getDOMDataTreeChangeService();
188 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
190 var writeTx = domBroker.newWriteOnlyTransaction();
191 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
192 writeTx.commit().get();
194 final var listener = new TestDataTreeListener(latch);
195 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
197 writeTx = domBroker.newWriteOnlyTransaction();
198 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
201 latch.await(5, TimeUnit.SECONDS);
203 assertEquals(2, listener.getReceivedChanges().size());
204 var changes = listener.getReceivedChanges().get(0);
205 assertEquals(1, changes.size());
207 var candidate = changes.get(0);
208 assertNotNull(candidate);
209 var candidateRoot = candidate.getRootNode();
210 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
212 changes = listener.getReceivedChanges().get(1);
213 assertEquals(1, changes.size());
215 candidate = changes.get(0);
216 assertNotNull(candidate);
217 candidateRoot = candidate.getRootNode();
218 checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
223 public void replaceChildListContainerInTreeTest() throws ExecutionException, InterruptedException {
224 final var latch = new CountDownLatch(2);
226 final var dataTreeChangeService = getDOMDataTreeChangeService();
227 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
229 var writeTx = domBroker.newWriteOnlyTransaction();
230 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
231 writeTx.commit().get();
233 final var listener = new TestDataTreeListener(latch);
234 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
236 writeTx = domBroker.newWriteOnlyTransaction();
237 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
240 latch.await(5, TimeUnit.SECONDS);
242 assertEquals(2, listener.getReceivedChanges().size());
243 var changes = listener.getReceivedChanges().get(0);
244 assertEquals(1, changes.size());
246 var candidate = changes.get(0);
247 assertNotNull(candidate);
248 var candidateRoot = candidate.getRootNode();
249 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
251 changes = listener.getReceivedChanges().get(1);
252 assertEquals(1, changes.size());
254 candidate = changes.get(0);
255 assertNotNull(candidate);
256 candidateRoot = candidate.getRootNode();
257 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
258 final var modifiedChild = candidateRoot.getModifiedChild(new NodeIdentifier(TestModel.OUTER_LIST_QNAME));
259 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
264 public void rootModificationChildListenerTest() throws ExecutionException, InterruptedException {
265 final var latch = new CountDownLatch(2);
267 final var dataTreeChangeService = getDOMDataTreeChangeService();
268 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
269 dataTreeChangeService);
271 var writeTx = domBroker.newWriteOnlyTransaction();
272 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
273 writeTx.commit().get();
275 final var listener = new TestDataTreeListener(latch);
276 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
278 writeTx = domBroker.newWriteOnlyTransaction();
279 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
280 writeTx.commit().get();
282 latch.await(1, TimeUnit.SECONDS);
284 assertEquals(2, listener.getReceivedChanges().size());
285 var changes = listener.getReceivedChanges().get(0);
286 assertEquals(1, changes.size());
288 var candidate = changes.get(0);
289 assertNotNull(candidate);
290 var candidateRoot = candidate.getRootNode();
291 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
293 changes = listener.getReceivedChanges().get(1);
294 assertEquals(1, changes.size());
296 candidate = changes.get(0);
297 assertNotNull(candidate);
298 candidateRoot = candidate.getRootNode();
299 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
304 public void listEntryChangeNonRootRegistrationTest() throws ExecutionException, InterruptedException {
305 final var latch = new CountDownLatch(2);
307 final var dataTreeChangeService = getDOMDataTreeChangeService();
308 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
310 var writeTx = domBroker.newWriteOnlyTransaction();
311 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
312 writeTx.commit().get();
314 final var listener = new TestDataTreeListener(latch);
315 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
317 final var outerListEntryId1 =
318 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
319 final var outerListEntryId2 =
320 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
321 final var outerListEntryId3 =
322 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
324 final var outerListEntry1 = TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
325 final var outerListEntry2 = TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
326 final var outerListEntry3 = TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
328 final var listAfter = ImmutableNodes.newSystemMapBuilder()
329 .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
330 .withChild(outerListEntry2)
331 .withChild(outerListEntry3)
334 writeTx = domBroker.newWriteOnlyTransaction();
335 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
336 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
338 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
342 latch.await(5, TimeUnit.SECONDS);
344 assertEquals(2, listener.getReceivedChanges().size());
345 var changes = listener.getReceivedChanges().get(0);
346 assertEquals(1, changes.size());
348 var candidate = changes.get(0);
349 assertNotNull(candidate);
350 var candidateRoot = candidate.getRootNode();
351 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
353 changes = listener.getReceivedChanges().get(1);
354 assertEquals(1, changes.size());
356 candidate = changes.get(0);
357 assertNotNull(candidate);
358 candidateRoot = candidate.getRootNode();
359 checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
360 final var entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1);
361 checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
362 final var entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2);
363 checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
364 final var entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3);
365 checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
369 private static void checkChange(final NormalizedNode expectedBefore, final NormalizedNode expectedAfter,
370 final ModificationType expectedMod, final DataTreeCandidateNode candidateNode) {
371 assertEquals(expectedBefore, candidateNode.dataBefore());
372 assertEquals(expectedAfter, candidateNode.dataAfter());
373 assertEquals(expectedMod, candidateNode.modificationType());
376 private DOMDataTreeChangeService getDOMDataTreeChangeService() {
377 return domBroker.extension(DOMDataTreeChangeService.class);
380 static class CommitExecutorService extends ForwardingExecutorService {
382 ExecutorService delegate;
384 CommitExecutorService(final ExecutorService delegate) {
385 this.delegate = delegate;
389 protected ExecutorService delegate() {
394 static class TestDataTreeListener implements DOMDataTreeChangeListener {
395 private final List<List<DataTreeCandidate>> receivedChanges = new ArrayList<>();
396 private final CountDownLatch latch;
398 TestDataTreeListener(final CountDownLatch latch) {
403 public void onDataTreeChanged(final List<DataTreeCandidate> changes) {
404 receivedChanges.add(changes);
409 public void onInitialData() {
413 List<List<DataTreeCandidate>> getReceivedChanges() {
414 return receivedChanges;