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.spi.node.ImmutableNodes;
43 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
44 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
45 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
47 public class DOMDataTreeListenerTest extends AbstractDatastoreTest {
49 private AbstractDOMDataBroker domBroker;
50 private ListeningExecutorService executor;
51 private ExecutorService futureExecutor;
52 private CommitExecutorService commitExecutor;
54 private static final MapNode OUTER_LIST = ImmutableNodes.newSystemMapBuilder()
55 .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
56 .withChild(TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1))
59 private static final MapNode OUTER_LIST_2 = ImmutableNodes.newSystemMapBuilder()
60 .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
61 .withChild(TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2))
64 private static final NormalizedNode TEST_CONTAINER = ImmutableNodes.newContainerBuilder()
65 .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
66 .withChild(OUTER_LIST)
69 private static final NormalizedNode TEST_CONTAINER_2 = ImmutableNodes.newContainerBuilder()
70 .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
71 .withChild(OUTER_LIST_2)
74 private static final DOMDataTreeIdentifier ROOT_DATA_TREE_ID =
75 DOMDataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
77 private static final DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID =
78 DOMDataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
81 public void setupStore() {
82 final InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER",
83 MoreExecutors.newDirectExecutorService());
84 final InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG",
85 MoreExecutors.newDirectExecutorService());
87 operStore.onModelContextUpdated(SCHEMA_CONTEXT);
88 configStore.onModelContextUpdated(SCHEMA_CONTEXT);
90 final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType,
92 .put(CONFIGURATION, configStore)
93 .put(OPERATIONAL, operStore)
96 commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
97 futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB",
98 DOMDataTreeListenerTest.class);
99 executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
100 TransactionCommitDeadlockException.DEADLOCK_EXCEPTION_SUPPLIER, futureExecutor);
101 domBroker = new SerializedDOMDataBroker(stores, executor);
105 public void tearDown() {
106 if (executor != null) {
107 executor.shutdownNow();
110 if (futureExecutor != null) {
111 futureExecutor.shutdownNow();
116 public void writeContainerEmptyTreeTest() throws InterruptedException {
117 final var latch = new CountDownLatch(1);
119 final var dataTreeChangeService = getDOMDataTreeChangeService();
120 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
121 dataTreeChangeService);
123 final var listener = new TestDataTreeListener(latch);
124 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
126 final var writeTx = domBroker.newWriteOnlyTransaction();
127 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
130 latch.await(5, TimeUnit.SECONDS);
132 assertEquals(1, listener.getReceivedChanges().size());
133 final var changes = listener.getReceivedChanges().get(0);
134 assertEquals(1, changes.size());
136 final var candidate = changes.get(0);
137 assertNotNull(candidate);
138 final var candidateRoot = candidate.getRootNode();
139 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
144 public void replaceContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
145 final var latch = new CountDownLatch(2);
147 final var dataTreeChangeService = getDOMDataTreeChangeService();
148 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
149 dataTreeChangeService);
151 var writeTx = domBroker.newWriteOnlyTransaction();
152 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
153 writeTx.commit().get();
155 final var listener = new TestDataTreeListener(latch);
156 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
157 writeTx = domBroker.newWriteOnlyTransaction();
158 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
161 latch.await(5, TimeUnit.SECONDS);
163 assertEquals(2, listener.getReceivedChanges().size());
164 var changes = listener.getReceivedChanges().get(0);
165 assertEquals(1, changes.size());
167 var candidate = changes.get(0);
168 assertNotNull(candidate);
169 var candidateRoot = candidate.getRootNode();
170 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
172 changes = listener.getReceivedChanges().get(1);
173 assertEquals(1, changes.size());
175 candidate = changes.get(0);
176 assertNotNull(candidate);
177 candidateRoot = candidate.getRootNode();
178 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
183 public void deleteContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
184 final var latch = new CountDownLatch(2);
186 final var dataTreeChangeService = getDOMDataTreeChangeService();
187 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
189 var writeTx = domBroker.newWriteOnlyTransaction();
190 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
191 writeTx.commit().get();
193 final var listener = new TestDataTreeListener(latch);
194 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
196 writeTx = domBroker.newWriteOnlyTransaction();
197 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
200 latch.await(5, TimeUnit.SECONDS);
202 assertEquals(2, listener.getReceivedChanges().size());
203 var changes = listener.getReceivedChanges().get(0);
204 assertEquals(1, changes.size());
206 var candidate = changes.get(0);
207 assertNotNull(candidate);
208 var candidateRoot = candidate.getRootNode();
209 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
211 changes = listener.getReceivedChanges().get(1);
212 assertEquals(1, changes.size());
214 candidate = changes.get(0);
215 assertNotNull(candidate);
216 candidateRoot = candidate.getRootNode();
217 checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
222 public void replaceChildListContainerInTreeTest() throws ExecutionException, InterruptedException {
223 final var latch = new CountDownLatch(2);
225 final var dataTreeChangeService = getDOMDataTreeChangeService();
226 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
228 var writeTx = domBroker.newWriteOnlyTransaction();
229 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
230 writeTx.commit().get();
232 final var listener = new TestDataTreeListener(latch);
233 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
235 writeTx = domBroker.newWriteOnlyTransaction();
236 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
239 latch.await(5, TimeUnit.SECONDS);
241 assertEquals(2, listener.getReceivedChanges().size());
242 var changes = listener.getReceivedChanges().get(0);
243 assertEquals(1, changes.size());
245 var candidate = changes.get(0);
246 assertNotNull(candidate);
247 var candidateRoot = candidate.getRootNode();
248 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
250 changes = listener.getReceivedChanges().get(1);
251 assertEquals(1, changes.size());
253 candidate = changes.get(0);
254 assertNotNull(candidate);
255 candidateRoot = candidate.getRootNode();
256 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
257 final var modifiedChild = candidateRoot.getModifiedChild(new NodeIdentifier(TestModel.OUTER_LIST_QNAME));
258 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
263 public void rootModificationChildListenerTest() throws ExecutionException, InterruptedException {
264 final var latch = new CountDownLatch(2);
266 final var dataTreeChangeService = getDOMDataTreeChangeService();
267 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
268 dataTreeChangeService);
270 var writeTx = domBroker.newWriteOnlyTransaction();
271 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
272 writeTx.commit().get();
274 final var listener = new TestDataTreeListener(latch);
275 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
277 writeTx = domBroker.newWriteOnlyTransaction();
278 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
279 writeTx.commit().get();
281 latch.await(1, TimeUnit.SECONDS);
283 assertEquals(2, listener.getReceivedChanges().size());
284 var changes = listener.getReceivedChanges().get(0);
285 assertEquals(1, changes.size());
287 var candidate = changes.get(0);
288 assertNotNull(candidate);
289 var candidateRoot = candidate.getRootNode();
290 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
292 changes = listener.getReceivedChanges().get(1);
293 assertEquals(1, changes.size());
295 candidate = changes.get(0);
296 assertNotNull(candidate);
297 candidateRoot = candidate.getRootNode();
298 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
303 public void listEntryChangeNonRootRegistrationTest() throws ExecutionException, InterruptedException {
304 final var latch = new CountDownLatch(2);
306 final var dataTreeChangeService = getDOMDataTreeChangeService();
307 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
309 var writeTx = domBroker.newWriteOnlyTransaction();
310 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
311 writeTx.commit().get();
313 final var listener = new TestDataTreeListener(latch);
314 final var listenerReg = dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
316 final var outerListEntryId1 =
317 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
318 final var outerListEntryId2 =
319 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
320 final var outerListEntryId3 =
321 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
323 final var outerListEntry1 = TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
324 final var outerListEntry2 = TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
325 final var outerListEntry3 = TestUtils.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
327 final var listAfter = ImmutableNodes.newSystemMapBuilder()
328 .withNodeIdentifier(new NodeIdentifier(TestModel.OUTER_LIST_QNAME))
329 .withChild(outerListEntry2)
330 .withChild(outerListEntry3)
333 writeTx = domBroker.newWriteOnlyTransaction();
334 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
335 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
337 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
341 latch.await(5, TimeUnit.SECONDS);
343 assertEquals(2, listener.getReceivedChanges().size());
344 var changes = listener.getReceivedChanges().get(0);
345 assertEquals(1, changes.size());
347 var candidate = changes.get(0);
348 assertNotNull(candidate);
349 var candidateRoot = candidate.getRootNode();
350 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
352 changes = listener.getReceivedChanges().get(1);
353 assertEquals(1, changes.size());
355 candidate = changes.get(0);
356 assertNotNull(candidate);
357 candidateRoot = candidate.getRootNode();
358 checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
359 final var entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1);
360 checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
361 final var entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2);
362 checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
363 final var entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3);
364 checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
368 private static void checkChange(final NormalizedNode expectedBefore, final NormalizedNode expectedAfter,
369 final ModificationType expectedMod, final DataTreeCandidateNode candidateNode) {
370 assertEquals(expectedBefore, candidateNode.dataBefore());
371 assertEquals(expectedAfter, candidateNode.dataAfter());
372 assertEquals(expectedMod, candidateNode.modificationType());
375 private DOMDataTreeChangeService getDOMDataTreeChangeService() {
376 return domBroker.extension(DOMDataTreeChangeService.class);
379 static class CommitExecutorService extends ForwardingExecutorService {
381 ExecutorService delegate;
383 CommitExecutorService(final ExecutorService delegate) {
384 this.delegate = delegate;
388 protected ExecutorService delegate() {
393 static class TestDataTreeListener implements DOMDataTreeChangeListener {
394 private final List<List<DataTreeCandidate>> receivedChanges = new ArrayList<>();
395 private final CountDownLatch latch;
397 TestDataTreeListener(final CountDownLatch latch) {
402 public void onDataTreeChanged(final List<DataTreeCandidate> changes) {
403 receivedChanges.add(changes);
408 public void onInitialData() {
412 List<List<DataTreeCandidate>> getReceivedChanges() {
413 return receivedChanges;