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.controller.md.sal.dom.broker.impl;
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.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
15 import static org.opendaylight.controller.md.sal.common.api.data.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 javax.annotation.Nonnull;
30 import org.junit.After;
31 import org.junit.Before;
32 import org.junit.Test;
33 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
34 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitDeadlockException;
35 import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension;
36 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
37 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
38 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
39 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
40 import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore;
41 import org.opendaylight.controller.md.sal.dom.store.impl.TestModel;
42 import org.opendaylight.controller.sal.core.spi.data.DOMStore;
43 import org.opendaylight.yangtools.concepts.ListenerRegistration;
44 import org.opendaylight.yangtools.util.concurrent.DeadlockDetectingListeningExecutorService;
45 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
47 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
48 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
52 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
54 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
55 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
56 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
58 public class DOMDataTreeListenerTest {
60 private SchemaContext schemaContext;
61 private AbstractDOMDataBroker domBroker;
62 private ListeningExecutorService executor;
63 private ExecutorService futureExecutor;
64 private CommitExecutorService commitExecutor;
66 private static final DataContainerChild<?, ?> OUTER_LIST = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
67 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1)).build();
69 private static final DataContainerChild<?, ?> OUTER_LIST_2 = ImmutableNodes
70 .mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
71 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2)).build();
73 private static final NormalizedNode<?, ?> TEST_CONTAINER = Builders.containerBuilder()
74 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME)).withChild(OUTER_LIST)
77 private static final NormalizedNode<?, ?> TEST_CONTAINER_2 = Builders.containerBuilder()
78 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME)).withChild(OUTER_LIST_2)
81 private static DOMDataTreeIdentifier ROOT_DATA_TREE_ID = new DOMDataTreeIdentifier(
82 LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
84 private static DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID = new DOMDataTreeIdentifier(
85 LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
88 public void setupStore() {
89 InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER", MoreExecutors.newDirectExecutorService());
90 InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG", MoreExecutors.newDirectExecutorService());
91 schemaContext = TestModel.createTestContext();
93 operStore.onGlobalContextUpdated(schemaContext);
94 configStore.onGlobalContextUpdated(schemaContext);
96 final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType,
98 .put(CONFIGURATION, configStore) //
99 .put(OPERATIONAL, operStore) //
102 commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
103 futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB",
104 DOMDataTreeListenerTest.class);
105 executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
106 TransactionCommitDeadlockException
107 .DEADLOCK_EXCEPTION_SUPPLIER,
109 domBroker = new SerializedDOMDataBroker(stores, executor);
113 public void tearDown() {
114 if (executor != null) {
115 executor.shutdownNow();
118 if (futureExecutor != null) {
119 futureExecutor.shutdownNow();
124 public void writeContainerEmptyTreeTest() throws InterruptedException {
125 CountDownLatch latch = new CountDownLatch(1);
127 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
128 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
130 final TestDataTreeListener listener = new TestDataTreeListener(latch);
131 final ListenerRegistration<TestDataTreeListener> listenerReg = dataTreeChangeService
132 .registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
134 final DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
135 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
138 latch.await(5, TimeUnit.SECONDS);
140 assertEquals(1, listener.getReceivedChanges().size());
141 final Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
142 assertEquals(1, changes.size());
144 DataTreeCandidate candidate = changes.iterator().next();
145 assertNotNull(candidate);
146 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
147 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
152 public void replaceContainerContainerInTreeTest() throws InterruptedException, ExecutionException {
153 final CountDownLatch latch = new CountDownLatch(2);
155 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
156 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
158 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
159 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
160 writeTx.commit().get();
162 final TestDataTreeListener listener = new TestDataTreeListener(latch);
163 final ListenerRegistration<TestDataTreeListener> listenerReg = dataTreeChangeService
164 .registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
165 writeTx = domBroker.newWriteOnlyTransaction();
166 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
169 latch.await(5, TimeUnit.SECONDS);
171 assertEquals(2, listener.getReceivedChanges().size());
172 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
173 assertEquals(1, changes.size());
175 DataTreeCandidate candidate = changes.iterator().next();
176 assertNotNull(candidate);
177 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
178 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
180 changes = listener.getReceivedChanges().get(1);
181 assertEquals(1, changes.size());
183 candidate = changes.iterator().next();
184 assertNotNull(candidate);
185 candidateRoot = candidate.getRootNode();
186 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
191 public void deleteContainerContainerInTreeTest() throws InterruptedException, ExecutionException {
192 final CountDownLatch latch = new CountDownLatch(2);
194 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
195 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
197 DOMDataWriteTransaction 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 = dataTreeChangeService
203 .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 InterruptedException, ExecutionException {
232 final CountDownLatch latch = new CountDownLatch(2);
234 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
235 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
237 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
238 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
239 writeTx.commit().get();
241 final TestDataTreeListener listener = new TestDataTreeListener(latch);
242 final ListenerRegistration<TestDataTreeListener> listenerReg = dataTreeChangeService
243 .registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
245 writeTx = domBroker.newWriteOnlyTransaction();
246 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
249 latch.await(5, TimeUnit.SECONDS);
251 assertEquals(2, listener.getReceivedChanges().size());
252 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
253 assertEquals(1, changes.size());
255 DataTreeCandidate candidate = changes.iterator().next();
256 assertNotNull(candidate);
257 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
258 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
260 changes = listener.getReceivedChanges().get(1);
261 assertEquals(1, changes.size());
263 candidate = changes.iterator().next();
264 assertNotNull(candidate);
265 candidateRoot = candidate.getRootNode();
266 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
267 final DataTreeCandidateNode modifiedChild = candidateRoot
268 .getModifiedChild(new YangInstanceIdentifier.NodeIdentifier(TestModel.OUTER_LIST_QNAME));
269 assertNotNull(modifiedChild);
270 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
275 public void rootModificationChildListenerTest() throws InterruptedException, ExecutionException {
276 final CountDownLatch latch = new CountDownLatch(2);
278 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
279 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
281 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
282 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
283 writeTx.commit().get();
285 final TestDataTreeListener listener = new TestDataTreeListener(latch);
286 final ListenerRegistration<TestDataTreeListener> listenerReg = dataTreeChangeService
287 .registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
289 writeTx = domBroker.newWriteOnlyTransaction();
290 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
291 writeTx.commit().get();
293 latch.await(1, TimeUnit.SECONDS);
295 assertEquals(2, listener.getReceivedChanges().size());
296 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
297 assertEquals(1, changes.size());
299 DataTreeCandidate candidate = changes.iterator().next();
300 assertNotNull(candidate);
301 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
302 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
304 changes = listener.getReceivedChanges().get(1);
305 assertEquals(1, changes.size());
307 candidate = changes.iterator().next();
308 assertNotNull(candidate);
309 candidateRoot = candidate.getRootNode();
310 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
315 public void listEntryChangeNonRootRegistrationTest() throws InterruptedException, ExecutionException {
316 final CountDownLatch latch = new CountDownLatch(2);
318 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
319 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!", dataTreeChangeService);
321 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
322 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
323 writeTx.commit().get();
325 final TestDataTreeListener listener = new TestDataTreeListener(latch);
326 final ListenerRegistration<TestDataTreeListener> listenerReg = dataTreeChangeService
327 .registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
329 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId1
330 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(TestModel.OUTER_LIST_QNAME,
331 TestModel.ID_QNAME, 1);
332 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId2
333 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(TestModel.OUTER_LIST_QNAME,
334 TestModel.ID_QNAME, 2);
335 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId3
336 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(TestModel.OUTER_LIST_QNAME,
337 TestModel.ID_QNAME, 3);
339 final MapEntryNode outerListEntry1 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
340 final MapEntryNode outerListEntry2 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
341 final MapEntryNode outerListEntry3 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
343 final MapNode listAfter = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).withChild(outerListEntry2)
344 .withChild(outerListEntry3).build();
346 writeTx = domBroker.newWriteOnlyTransaction();
347 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
348 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
350 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
354 latch.await(5, TimeUnit.SECONDS);
356 assertEquals(2, listener.getReceivedChanges().size());
357 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
358 assertEquals(1, changes.size());
360 DataTreeCandidate candidate = changes.iterator().next();
361 assertNotNull(candidate);
362 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
363 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
365 changes = listener.getReceivedChanges().get(1);
366 assertEquals(1, changes.size());
368 candidate = changes.iterator().next();
369 assertNotNull(candidate);
370 candidateRoot = candidate.getRootNode();
371 checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
372 final DataTreeCandidateNode entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1);
373 checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
374 final DataTreeCandidateNode entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2);
375 checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
376 final DataTreeCandidateNode entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3);
377 checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
381 private static void checkChange(final NormalizedNode<?, ?> expectedBefore, final NormalizedNode<?, ?> expectedAfter,
382 final ModificationType expectedMod, final DataTreeCandidateNode candidateNode) {
383 if (expectedBefore != null) {
384 assertTrue(candidateNode.getDataBefore().isPresent());
385 assertEquals(expectedBefore, candidateNode.getDataBefore().get());
387 assertFalse(candidateNode.getDataBefore().isPresent());
390 if (expectedAfter != null) {
391 assertTrue(candidateNode.getDataAfter().isPresent());
392 assertEquals(expectedAfter, candidateNode.getDataAfter().get());
394 assertFalse(candidateNode.getDataAfter().isPresent());
397 assertEquals(expectedMod, candidateNode.getModificationType());
400 private DOMDataTreeChangeService getDOMDataTreeChangeService() {
401 final DOMDataBrokerExtension extension = domBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class);
402 if (extension == null) {
405 DOMDataTreeChangeService dataTreeChangeService = null;
406 if (extension instanceof DOMDataTreeChangeService) {
407 dataTreeChangeService = (DOMDataTreeChangeService) extension;
409 return dataTreeChangeService;
413 static class CommitExecutorService extends ForwardingExecutorService {
415 ExecutorService delegate;
417 CommitExecutorService(final ExecutorService delegate) {
418 this.delegate = delegate;
422 protected ExecutorService delegate() {
427 static class TestDataTreeListener implements DOMDataTreeChangeListener {
429 private final List<Collection<DataTreeCandidate>> receivedChanges = new ArrayList<>();
430 private final CountDownLatch latch;
432 TestDataTreeListener(final CountDownLatch latch) {
437 public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
438 receivedChanges.add(changes);
442 public List<Collection<DataTreeCandidate>> getReceivedChanges() {
443 return receivedChanges;