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.mdsal.dom.broker;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertFalse;
13 import static org.junit.Assert.assertNotNull;
14 import static org.junit.Assert.assertTrue;
15 import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
16 import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;
18 import com.google.common.collect.ImmutableMap;
19 import com.google.common.util.concurrent.ForwardingExecutorService;
20 import com.google.common.util.concurrent.ListeningExecutorService;
21 import com.google.common.util.concurrent.MoreExecutors;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.concurrent.CountDownLatch;
26 import java.util.concurrent.ExecutionException;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.TimeUnit;
30 import javax.annotation.Nonnull;
31 import org.junit.After;
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
35 import org.opendaylight.mdsal.common.api.TransactionCommitDeadlockException;
36 import org.opendaylight.mdsal.dom.api.DOMDataBrokerExtension;
37 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
38 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
39 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
40 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
41 import org.opendaylight.mdsal.dom.broker.util.TestModel;
42 import org.opendaylight.mdsal.dom.spi.store.DOMStore;
43 import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataStore;
44 import org.opendaylight.yangtools.concepts.ListenerRegistration;
45 import org.opendaylight.yangtools.util.concurrent.DeadlockDetectingListeningExecutorService;
46 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
49 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
53 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
55 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
56 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
57 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
59 public class DOMDataTreeListenerTest {
61 private SchemaContext schemaContext;
62 private AbstractDOMDataBroker domBroker;
63 private ListeningExecutorService executor;
64 private ExecutorService futureExecutor;
65 private CommitExecutorService commitExecutor;
67 private static final DataContainerChild<?, ?> OUTER_LIST =
68 ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
69 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1))
72 private static final DataContainerChild<?, ?> OUTER_LIST_2 =
73 ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
74 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2))
77 private static final NormalizedNode<?, ?> TEST_CONTAINER = Builders.containerBuilder()
78 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
79 .withChild(OUTER_LIST)
82 private static final NormalizedNode<?, ?> TEST_CONTAINER_2 = Builders.containerBuilder()
83 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
84 .withChild(OUTER_LIST_2)
87 private static final DOMDataTreeIdentifier ROOT_DATA_TREE_ID = new DOMDataTreeIdentifier(
88 LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
90 private static final DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID = new DOMDataTreeIdentifier(
91 LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
94 public void setupStore() throws Exception {
95 final InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER",
96 MoreExecutors.newDirectExecutorService());
97 final InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG",
98 MoreExecutors.newDirectExecutorService());
99 schemaContext = TestModel.createTestContext();
101 operStore.onGlobalContextUpdated(schemaContext);
102 configStore.onGlobalContextUpdated(schemaContext);
104 final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType,
106 .put(CONFIGURATION, configStore)
107 .put(OPERATIONAL, operStore)
110 commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
111 futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB");
112 executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
113 TransactionCommitDeadlockException.DEADLOCK_EXCEPTION_SUPPLIER, futureExecutor);
114 domBroker = new SerializedDOMDataBroker(stores, executor);
118 public void tearDown() {
119 if (executor != null) {
120 executor.shutdownNow();
123 if (futureExecutor != null) {
124 futureExecutor.shutdownNow();
129 public void writeContainerEmptyTreeTest() throws InterruptedException {
130 final CountDownLatch latch = new CountDownLatch(1);
132 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
133 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
134 dataTreeChangeService);
136 final TestDataTreeListener listener = new TestDataTreeListener(latch);
137 final ListenerRegistration<TestDataTreeListener> listenerReg =
138 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
140 final DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
141 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
144 latch.await(5, TimeUnit.SECONDS);
146 assertEquals(1, listener.getReceivedChanges().size());
147 final Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
148 assertEquals(1, changes.size());
150 final DataTreeCandidate candidate = changes.iterator().next();
151 assertNotNull(candidate);
152 final DataTreeCandidateNode candidateRoot = candidate.getRootNode();
153 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
158 public void replaceContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
159 final CountDownLatch latch = new CountDownLatch(2);
161 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
162 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
163 dataTreeChangeService);
165 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
166 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
167 writeTx.submit().get();
169 final TestDataTreeListener listener = new TestDataTreeListener(latch);
170 final ListenerRegistration<TestDataTreeListener> listenerReg =
171 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
172 writeTx = domBroker.newWriteOnlyTransaction();
173 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
176 latch.await(5, TimeUnit.SECONDS);
178 assertEquals(2, listener.getReceivedChanges().size());
179 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
180 assertEquals(1, changes.size());
182 DataTreeCandidate candidate = changes.iterator().next();
183 assertNotNull(candidate);
184 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
185 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
187 changes = listener.getReceivedChanges().get(1);
188 assertEquals(1, changes.size());
190 candidate = changes.iterator().next();
191 assertNotNull(candidate);
192 candidateRoot = candidate.getRootNode();
193 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
198 public void deleteContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
199 final CountDownLatch latch = new CountDownLatch(2);
201 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
202 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
203 dataTreeChangeService);
205 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
206 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
207 writeTx.submit().get();
209 final TestDataTreeListener listener = new TestDataTreeListener(latch);
210 final ListenerRegistration<TestDataTreeListener> listenerReg =
211 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
213 writeTx = domBroker.newWriteOnlyTransaction();
214 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
217 latch.await(5, TimeUnit.SECONDS);
219 assertEquals(2, listener.getReceivedChanges().size());
220 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
221 assertEquals(1, changes.size());
223 DataTreeCandidate candidate = changes.iterator().next();
224 assertNotNull(candidate);
225 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
226 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
228 changes = listener.getReceivedChanges().get(1);
229 assertEquals(1, changes.size());
231 candidate = changes.iterator().next();
232 assertNotNull(candidate);
233 candidateRoot = candidate.getRootNode();
234 checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
239 public void replaceChildListContainerInTreeTest() throws ExecutionException, InterruptedException {
240 final CountDownLatch latch = new CountDownLatch(2);
242 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
243 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
244 dataTreeChangeService);
246 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
247 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
248 writeTx.submit().get();
250 final TestDataTreeListener listener = new TestDataTreeListener(latch);
251 final ListenerRegistration<TestDataTreeListener> listenerReg =
252 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
254 writeTx = domBroker.newWriteOnlyTransaction();
255 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
258 latch.await(5, TimeUnit.SECONDS);
260 assertEquals(2, listener.getReceivedChanges().size());
261 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
262 assertEquals(1, changes.size());
264 DataTreeCandidate candidate = changes.iterator().next();
265 assertNotNull(candidate);
266 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
267 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
269 changes = listener.getReceivedChanges().get(1);
270 assertEquals(1, changes.size());
272 candidate = changes.iterator().next();
273 assertNotNull(candidate);
274 candidateRoot = candidate.getRootNode();
275 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
276 final DataTreeCandidateNode modifiedChild = candidateRoot.getModifiedChild(
277 new YangInstanceIdentifier.NodeIdentifier(TestModel.OUTER_LIST_QNAME));
278 assertNotNull(modifiedChild);
279 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
284 public void rootModificationChildListenerTest() throws ExecutionException, InterruptedException {
285 final CountDownLatch latch = new CountDownLatch(2);
287 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
288 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
289 dataTreeChangeService);
291 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
292 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
293 writeTx.submit().get();
295 final TestDataTreeListener listener = new TestDataTreeListener(latch);
296 final ListenerRegistration<TestDataTreeListener> listenerReg =
297 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
299 writeTx = domBroker.newWriteOnlyTransaction();
300 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
301 writeTx.submit().get();
303 latch.await(1, TimeUnit.SECONDS);
305 assertEquals(2, listener.getReceivedChanges().size());
306 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
307 assertEquals(1, changes.size());
309 DataTreeCandidate candidate = changes.iterator().next();
310 assertNotNull(candidate);
311 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
312 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
314 changes = listener.getReceivedChanges().get(1);
315 assertEquals(1, changes.size());
317 candidate = changes.iterator().next();
318 assertNotNull(candidate);
319 candidateRoot = candidate.getRootNode();
320 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
325 public void listEntryChangeNonRootRegistrationTest() throws ExecutionException, InterruptedException {
326 final CountDownLatch latch = new CountDownLatch(2);
328 final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
329 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
330 dataTreeChangeService);
332 DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
333 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
334 writeTx.submit().get();
336 final TestDataTreeListener listener = new TestDataTreeListener(latch);
337 final ListenerRegistration<TestDataTreeListener> listenerReg =
338 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
340 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId1 =
341 new YangInstanceIdentifier.NodeIdentifierWithPredicates(
342 TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
343 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId2 =
344 new YangInstanceIdentifier.NodeIdentifierWithPredicates(
345 TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
346 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId3 =
347 new YangInstanceIdentifier.NodeIdentifierWithPredicates(
348 TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
350 final MapEntryNode outerListEntry1 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
351 final MapEntryNode outerListEntry2 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
352 final MapEntryNode outerListEntry3 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
354 final MapNode listAfter = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
355 .withChild(outerListEntry2)
356 .withChild(outerListEntry3)
359 writeTx = domBroker.newWriteOnlyTransaction();
360 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
361 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
363 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
367 latch.await(5, TimeUnit.SECONDS);
369 assertEquals(2, listener.getReceivedChanges().size());
370 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
371 assertEquals(1, changes.size());
373 DataTreeCandidate candidate = changes.iterator().next();
374 assertNotNull(candidate);
375 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
376 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
378 changes = listener.getReceivedChanges().get(1);
379 assertEquals(1, changes.size());
381 candidate = changes.iterator().next();
382 assertNotNull(candidate);
383 candidateRoot = candidate.getRootNode();
384 checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
385 final DataTreeCandidateNode entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1);
386 checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
387 final DataTreeCandidateNode entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2);
388 checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
389 final DataTreeCandidateNode entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3);
390 checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
394 private static void checkChange(final NormalizedNode<?, ?> expectedBefore,
395 final NormalizedNode<?, ?> expectedAfter,
396 final ModificationType expectedMod,
397 final DataTreeCandidateNode candidateNode) {
398 if (expectedBefore != null) {
399 assertTrue(candidateNode.getDataBefore().isPresent());
400 assertEquals(expectedBefore, candidateNode.getDataBefore().get());
402 assertFalse(candidateNode.getDataBefore().isPresent());
405 if (expectedAfter != null) {
406 assertTrue(candidateNode.getDataAfter().isPresent());
407 assertEquals(expectedAfter, candidateNode.getDataAfter().get());
409 assertFalse(candidateNode.getDataAfter().isPresent());
412 assertEquals(expectedMod, candidateNode.getModificationType());
415 private DOMDataTreeChangeService getDOMDataTreeChangeService() {
416 final DOMDataBrokerExtension extension = domBroker.getSupportedExtensions()
417 .get(DOMDataTreeChangeService.class);
418 if (extension == null) {
421 DOMDataTreeChangeService dataTreeChangeService = null;
422 if (extension instanceof DOMDataTreeChangeService) {
423 dataTreeChangeService = (DOMDataTreeChangeService) extension;
425 return dataTreeChangeService;
428 static class CommitExecutorService extends ForwardingExecutorService {
430 ExecutorService delegate;
432 CommitExecutorService(final ExecutorService delegate) {
433 this.delegate = delegate;
437 protected ExecutorService delegate() {
442 static class TestDataTreeListener implements DOMDataTreeChangeListener {
444 private final List<Collection<DataTreeCandidate>> receivedChanges = new ArrayList<>();
445 private final CountDownLatch latch;
447 TestDataTreeListener(final CountDownLatch latch) {
452 public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
453 receivedChanges.add(changes);
457 public List<Collection<DataTreeCandidate>> getReceivedChanges() {
458 return receivedChanges;