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.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertTrue;
14 import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
15 import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.util.concurrent.ForwardingExecutorService;
19 import com.google.common.util.concurrent.ListeningExecutorService;
20 import com.google.common.util.concurrent.MoreExecutors;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.List;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.TimeUnit;
29 import org.junit.After;
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
33 import org.opendaylight.mdsal.common.api.TransactionCommitDeadlockException;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
35 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
36 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
37 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
38 import org.opendaylight.mdsal.dom.spi.store.DOMStore;
39 import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataStore;
40 import org.opendaylight.yangtools.concepts.ListenerRegistration;
41 import org.opendaylight.yangtools.util.concurrent.DeadlockDetectingListeningExecutorService;
42 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
45 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
46 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
49 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
51 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
52 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
54 public class DOMDataTreeListenerTest extends AbstractDatastoreTest {
56 private AbstractDOMDataBroker domBroker;
57 private ListeningExecutorService executor;
58 private ExecutorService futureExecutor;
59 private CommitExecutorService commitExecutor;
61 private static final MapNode OUTER_LIST = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
62 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1))
65 private static final MapNode OUTER_LIST_2 = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
66 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2))
69 private static final NormalizedNode TEST_CONTAINER = Builders.containerBuilder()
70 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
71 .withChild(OUTER_LIST)
74 private static final NormalizedNode TEST_CONTAINER_2 = Builders.containerBuilder()
75 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
76 .withChild(OUTER_LIST_2)
79 private static final DOMDataTreeIdentifier ROOT_DATA_TREE_ID = new DOMDataTreeIdentifier(
80 LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
82 private static final DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID = new DOMDataTreeIdentifier(
83 LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
86 public void setupStore() {
87 final InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER",
88 MoreExecutors.newDirectExecutorService());
89 final InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG",
90 MoreExecutors.newDirectExecutorService());
92 operStore.onModelContextUpdated(SCHEMA_CONTEXT);
93 configStore.onModelContextUpdated(SCHEMA_CONTEXT);
95 final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType,
97 .put(CONFIGURATION, configStore)
98 .put(OPERATIONAL, operStore)
101 commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
102 futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB",
103 DOMDataTreeListenerTest.class);
104 executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
105 TransactionCommitDeadlockException.DEADLOCK_EXCEPTION_SUPPLIER, futureExecutor);
106 domBroker = new SerializedDOMDataBroker(stores, executor);
110 public void tearDown() {
111 if (executor != null) {
112 executor.shutdownNow();
115 if (futureExecutor != null) {
116 futureExecutor.shutdownNow();
121 public void writeContainerEmptyTreeTest() throws InterruptedException {
122 final CountDownLatch latch = new CountDownLatch(1);
124 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
125 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
126 dataTreeChangeService);
128 final TestDataTreeListener listener = new TestDataTreeListener(latch);
129 final ListenerRegistration<TestDataTreeListener> listenerReg =
130 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
132 final DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
133 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
136 latch.await(5, TimeUnit.SECONDS);
138 assertEquals(1, listener.getReceivedChanges().size());
139 final Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
140 assertEquals(1, changes.size());
142 final DataTreeCandidate candidate = changes.iterator().next();
143 assertNotNull(candidate);
144 final DataTreeCandidateNode candidateRoot = candidate.getRootNode();
145 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
150 public void replaceContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
151 final CountDownLatch latch = new CountDownLatch(2);
153 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
154 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
155 dataTreeChangeService);
157 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
158 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
159 writeTx.commit().get();
161 final TestDataTreeListener listener = new TestDataTreeListener(latch);
162 final ListenerRegistration<TestDataTreeListener> listenerReg =
163 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
164 writeTx = domBroker.newWriteOnlyTransaction();
165 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
168 latch.await(5, TimeUnit.SECONDS);
170 assertEquals(2, listener.getReceivedChanges().size());
171 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
172 assertEquals(1, changes.size());
174 DataTreeCandidate candidate = changes.iterator().next();
175 assertNotNull(candidate);
176 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
177 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
179 changes = listener.getReceivedChanges().get(1);
180 assertEquals(1, changes.size());
182 candidate = changes.iterator().next();
183 assertNotNull(candidate);
184 candidateRoot = candidate.getRootNode();
185 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
190 public void deleteContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
191 final CountDownLatch latch = new CountDownLatch(2);
193 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
194 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
195 dataTreeChangeService);
197 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
198 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
199 writeTx.commit().get();
201 final TestDataTreeListener listener = new TestDataTreeListener(latch);
202 final ListenerRegistration<TestDataTreeListener> listenerReg =
203 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
205 writeTx = domBroker.newWriteOnlyTransaction();
206 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
209 latch.await(5, TimeUnit.SECONDS);
211 assertEquals(2, listener.getReceivedChanges().size());
212 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
213 assertEquals(1, changes.size());
215 DataTreeCandidate candidate = changes.iterator().next();
216 assertNotNull(candidate);
217 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
218 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
220 changes = listener.getReceivedChanges().get(1);
221 assertEquals(1, changes.size());
223 candidate = changes.iterator().next();
224 assertNotNull(candidate);
225 candidateRoot = candidate.getRootNode();
226 checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
231 public void replaceChildListContainerInTreeTest() throws ExecutionException, InterruptedException {
232 final CountDownLatch latch = new CountDownLatch(2);
234 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
235 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
236 dataTreeChangeService);
238 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
239 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
240 writeTx.commit().get();
242 final TestDataTreeListener listener = new TestDataTreeListener(latch);
243 final ListenerRegistration<TestDataTreeListener> listenerReg =
244 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
246 writeTx = domBroker.newWriteOnlyTransaction();
247 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
250 latch.await(5, TimeUnit.SECONDS);
252 assertEquals(2, listener.getReceivedChanges().size());
253 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
254 assertEquals(1, changes.size());
256 DataTreeCandidate candidate = changes.iterator().next();
257 assertNotNull(candidate);
258 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
259 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
261 changes = listener.getReceivedChanges().get(1);
262 assertEquals(1, changes.size());
264 candidate = changes.iterator().next();
265 assertNotNull(candidate);
266 candidateRoot = candidate.getRootNode();
267 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
268 final DataTreeCandidateNode modifiedChild = candidateRoot.getModifiedChild(
269 new YangInstanceIdentifier.NodeIdentifier(TestModel.OUTER_LIST_QNAME)).get();
270 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
275 public void rootModificationChildListenerTest() throws ExecutionException, InterruptedException {
276 final CountDownLatch latch = new CountDownLatch(2);
278 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
279 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
280 dataTreeChangeService);
282 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
283 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
284 writeTx.commit().get();
286 final TestDataTreeListener listener = new TestDataTreeListener(latch);
287 final ListenerRegistration<TestDataTreeListener> listenerReg =
288 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
290 writeTx = domBroker.newWriteOnlyTransaction();
291 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
292 writeTx.commit().get();
294 latch.await(1, TimeUnit.SECONDS);
296 assertEquals(2, listener.getReceivedChanges().size());
297 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
298 assertEquals(1, changes.size());
300 DataTreeCandidate candidate = changes.iterator().next();
301 assertNotNull(candidate);
302 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
303 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
305 changes = listener.getReceivedChanges().get(1);
306 assertEquals(1, changes.size());
308 candidate = changes.iterator().next();
309 assertNotNull(candidate);
310 candidateRoot = candidate.getRootNode();
311 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
316 public void listEntryChangeNonRootRegistrationTest() throws ExecutionException, InterruptedException {
317 final CountDownLatch latch = new CountDownLatch(2);
319 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
320 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
321 dataTreeChangeService);
323 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
324 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
325 writeTx.commit().get();
327 final TestDataTreeListener listener = new TestDataTreeListener(latch);
328 final ListenerRegistration<TestDataTreeListener> listenerReg =
329 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
331 final NodeIdentifierWithPredicates outerListEntryId1 =
332 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
333 final NodeIdentifierWithPredicates outerListEntryId2 =
334 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
335 final NodeIdentifierWithPredicates outerListEntryId3 =
336 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
338 final MapEntryNode outerListEntry1 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
339 final MapEntryNode outerListEntry2 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
340 final MapEntryNode outerListEntry3 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
342 final MapNode listAfter = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
343 .withChild(outerListEntry2)
344 .withChild(outerListEntry3)
347 writeTx = domBroker.newWriteOnlyTransaction();
348 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
349 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
351 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
355 latch.await(5, TimeUnit.SECONDS);
357 assertEquals(2, listener.getReceivedChanges().size());
358 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
359 assertEquals(1, changes.size());
361 DataTreeCandidate candidate = changes.iterator().next();
362 assertNotNull(candidate);
363 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
364 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
366 changes = listener.getReceivedChanges().get(1);
367 assertEquals(1, changes.size());
369 candidate = changes.iterator().next();
370 assertNotNull(candidate);
371 candidateRoot = candidate.getRootNode();
372 checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
373 final DataTreeCandidateNode entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1).get();
374 checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
375 final DataTreeCandidateNode entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2).get();
376 checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
377 final DataTreeCandidateNode entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3).get();
378 checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
382 private static void checkChange(final NormalizedNode expectedBefore, final NormalizedNode expectedAfter,
383 final ModificationType expectedMod, final DataTreeCandidateNode candidateNode) {
384 if (expectedBefore != null) {
385 assertTrue(candidateNode.getDataBefore().isPresent());
386 assertEquals(expectedBefore, candidateNode.getDataBefore().get());
388 assertFalse(candidateNode.getDataBefore().isPresent());
391 if (expectedAfter != null) {
392 assertTrue(candidateNode.getDataAfter().isPresent());
393 assertEquals(expectedAfter, candidateNode.getDataAfter().get());
395 assertFalse(candidateNode.getDataAfter().isPresent());
398 assertEquals(expectedMod, candidateNode.getModificationType());
401 private DOMDataTreeChangeService getDOMDataTreeChangeService() {
402 return domBroker.getExtensions().getInstance(DOMDataTreeChangeService.class);
405 static class CommitExecutorService extends ForwardingExecutorService {
407 ExecutorService delegate;
409 CommitExecutorService(final ExecutorService delegate) {
410 this.delegate = delegate;
414 protected ExecutorService delegate() {
419 static class TestDataTreeListener implements DOMDataTreeChangeListener {
420 private final List<Collection<DataTreeCandidate>> receivedChanges = new ArrayList<>();
421 private final CountDownLatch latch;
423 TestDataTreeListener(final CountDownLatch latch) {
428 public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
429 receivedChanges.add(changes);
434 public void onInitialData() {
438 List<Collection<DataTreeCandidate>> getReceivedChanges() {
439 return receivedChanges;