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.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
15 import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;
17 import org.opendaylight.mdsal.dom.spi.store.DOMStore;
19 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
20 import org.opendaylight.mdsal.common.api.TransactionCommitDeadlockException;
21 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
22 import org.opendaylight.mdsal.dom.broker.AbstractDOMDataBroker;
23 import org.opendaylight.mdsal.dom.broker.SerializedDOMDataBroker;
24 import org.opendaylight.mdsal.dom.api.DOMDataBrokerExtension;
25 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
26 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
28 import org.opendaylight.mdsal.dom.api.DOMDataWriteTransaction;
29 import com.google.common.collect.ImmutableMap;
30 import com.google.common.util.concurrent.ForwardingExecutorService;
31 import com.google.common.util.concurrent.ListeningExecutorService;
32 import com.google.common.util.concurrent.MoreExecutors;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.concurrent.CountDownLatch;
37 import java.util.concurrent.ExecutorService;
38 import java.util.concurrent.Executors;
39 import java.util.concurrent.TimeUnit;
40 import javax.annotation.Nonnull;
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore;
45 import org.opendaylight.controller.md.sal.dom.store.impl.TestModel;
46 import org.opendaylight.yangtools.concepts.ListenerRegistration;
47 import org.opendaylight.yangtools.util.concurrent.DeadlockDetectingListeningExecutorService;
48 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
49 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
50 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
51 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
55 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
57 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
58 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
59 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
61 public class DOMDataTreeListenerTest {
63 private SchemaContext schemaContext;
64 private AbstractDOMDataBroker domBroker;
65 private ListeningExecutorService executor;
66 private ExecutorService futureExecutor;
67 private CommitExecutorService commitExecutor;
69 private static final DataContainerChild<?, ?> OUTER_LIST = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
70 .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1))
73 private static final DataContainerChild<?, ?> OUTER_LIST_2 = 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 DOMDataTreeIdentifier ROOT_DATA_TREE_ID = new DOMDataTreeIdentifier(
88 LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
90 private static DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID = new DOMDataTreeIdentifier(
91 LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
94 public void setupStore() {
95 InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER",
96 MoreExecutors.newDirectExecutorService());
97 InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG",
98 MoreExecutors.newDirectExecutorService());
99 schemaContext = TestModel.createTestContext();
101 operStore.onGlobalContextUpdated(schemaContext);
102 configStore.onGlobalContextUpdated(schemaContext);
104 ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType, DOMStore>builder() //
105 .put(CONFIGURATION, configStore) //
106 .put(OPERATIONAL, operStore) //
109 commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
110 futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB");
111 executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
112 TransactionCommitDeadlockException.DEADLOCK_EXCEPTION_SUPPLIER, futureExecutor);
113 domBroker = new SerializedDOMDataBroker(stores, executor);
117 public void tearDown() {
118 if (executor != null) {
119 executor.shutdownNow();
122 if (futureExecutor != null) {
123 futureExecutor.shutdownNow();
128 public void writeContainerEmptyTreeTest() throws InterruptedException {
129 CountDownLatch latch = new CountDownLatch(1);
131 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
132 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
133 dataTreeChangeService);
135 final TestDataTreeListener listener = new TestDataTreeListener(latch);
136 final ListenerRegistration<TestDataTreeListener> listenerReg =
137 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
139 final DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
140 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
143 latch.await(5, TimeUnit.SECONDS);
145 assertEquals(1, listener.getReceivedChanges().size());
146 final Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
147 assertEquals(1, changes.size());
149 DataTreeCandidate candidate = changes.iterator().next();
150 assertNotNull(candidate);
151 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
152 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
157 public void replaceContainerContainerInTreeTest() throws InterruptedException, TransactionCommitFailedException {
158 CountDownLatch latch = new CountDownLatch(2);
160 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
161 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
162 dataTreeChangeService);
164 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
165 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
166 writeTx.submit().checkedGet();
168 final TestDataTreeListener listener = new TestDataTreeListener(latch);
169 final ListenerRegistration<TestDataTreeListener> listenerReg =
170 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
171 writeTx = domBroker.newWriteOnlyTransaction();
172 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
175 latch.await(5, TimeUnit.SECONDS);
177 assertEquals(2, listener.getReceivedChanges().size());
178 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
179 assertEquals(1, changes.size());
181 DataTreeCandidate candidate = changes.iterator().next();
182 assertNotNull(candidate);
183 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
184 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
186 changes = listener.getReceivedChanges().get(1);
187 assertEquals(1, changes.size());
189 candidate = changes.iterator().next();
190 assertNotNull(candidate);
191 candidateRoot = candidate.getRootNode();
192 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
197 public void deleteContainerContainerInTreeTest() throws InterruptedException, TransactionCommitFailedException {
198 CountDownLatch latch = new CountDownLatch(2);
200 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
201 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
202 dataTreeChangeService);
204 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
205 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
206 writeTx.submit().checkedGet();
208 final TestDataTreeListener listener = new TestDataTreeListener(latch);
209 final ListenerRegistration<TestDataTreeListener> listenerReg =
210 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
212 writeTx = domBroker.newWriteOnlyTransaction();
213 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
216 latch.await(5, TimeUnit.SECONDS);
218 assertEquals(2, listener.getReceivedChanges().size());
219 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
220 assertEquals(1, changes.size());
222 DataTreeCandidate candidate = changes.iterator().next();
223 assertNotNull(candidate);
224 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
225 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
227 changes = listener.getReceivedChanges().get(1);
228 assertEquals(1, changes.size());
230 candidate = changes.iterator().next();
231 assertNotNull(candidate);
232 candidateRoot = candidate.getRootNode();
233 checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
238 public void replaceChildListContainerInTreeTest() throws InterruptedException, TransactionCommitFailedException {
239 CountDownLatch latch = new CountDownLatch(2);
241 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
242 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
243 dataTreeChangeService);
245 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
246 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
247 writeTx.submit().checkedGet();
249 final TestDataTreeListener listener = new TestDataTreeListener(latch);
250 final ListenerRegistration<TestDataTreeListener> listenerReg =
251 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
253 writeTx = domBroker.newWriteOnlyTransaction();
254 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
257 latch.await(5, TimeUnit.SECONDS);
259 assertEquals(2, listener.getReceivedChanges().size());
260 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
261 assertEquals(1, changes.size());
263 DataTreeCandidate candidate = changes.iterator().next();
264 assertNotNull(candidate);
265 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
266 checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
268 changes = listener.getReceivedChanges().get(1);
269 assertEquals(1, changes.size());
271 candidate = changes.iterator().next();
272 assertNotNull(candidate);
273 candidateRoot = candidate.getRootNode();
274 checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
275 final DataTreeCandidateNode modifiedChild = candidateRoot.getModifiedChild(
276 new YangInstanceIdentifier.NodeIdentifier(TestModel.OUTER_LIST_QNAME));
277 assertNotNull(modifiedChild);
278 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
283 public void rootModificationChildListenerTest() throws InterruptedException, TransactionCommitFailedException {
284 CountDownLatch latch = new CountDownLatch(2);
286 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
287 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
288 dataTreeChangeService);
290 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
291 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
292 writeTx.submit().checkedGet();
294 final TestDataTreeListener listener = new TestDataTreeListener(latch);
295 final ListenerRegistration<TestDataTreeListener> listenerReg =
296 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
298 writeTx = domBroker.newWriteOnlyTransaction();
299 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
300 writeTx.submit().checkedGet();
302 latch.await(1, TimeUnit.SECONDS);
304 assertEquals(2, listener.getReceivedChanges().size());
305 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
306 assertEquals(1, changes.size());
308 DataTreeCandidate candidate = changes.iterator().next();
309 assertNotNull(candidate);
310 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
311 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
313 changes = listener.getReceivedChanges().get(1);
314 assertEquals(1, changes.size());
316 candidate = changes.iterator().next();
317 assertNotNull(candidate);
318 candidateRoot = candidate.getRootNode();
319 checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
324 public void listEntryChangeNonRootRegistrationTest() throws InterruptedException, TransactionCommitFailedException {
325 CountDownLatch latch = new CountDownLatch(2);
327 DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
328 assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
329 dataTreeChangeService);
331 DOMDataWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
332 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
333 writeTx.submit().checkedGet();
335 final TestDataTreeListener listener = new TestDataTreeListener(latch);
336 final ListenerRegistration<TestDataTreeListener> listenerReg =
337 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
339 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId1 =
340 new YangInstanceIdentifier.NodeIdentifierWithPredicates(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
341 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId2 =
342 new YangInstanceIdentifier.NodeIdentifierWithPredicates(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
343 final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId3 =
344 new YangInstanceIdentifier.NodeIdentifierWithPredicates(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
346 final MapEntryNode outerListEntry1 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
347 final MapEntryNode outerListEntry2 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
348 final MapEntryNode outerListEntry3 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
350 final MapNode listAfter = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
351 .withChild(outerListEntry2)
352 .withChild(outerListEntry3)
355 writeTx = domBroker.newWriteOnlyTransaction();
356 writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
357 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
359 writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
363 latch.await(5, TimeUnit.SECONDS);
365 assertEquals(2, listener.getReceivedChanges().size());
366 Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
367 assertEquals(1, changes.size());
369 DataTreeCandidate candidate = changes.iterator().next();
370 assertNotNull(candidate);
371 DataTreeCandidateNode candidateRoot = candidate.getRootNode();
372 checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
374 changes = listener.getReceivedChanges().get(1);
375 assertEquals(1, changes.size());
377 candidate = changes.iterator().next();
378 assertNotNull(candidate);
379 candidateRoot = candidate.getRootNode();
380 checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
381 final DataTreeCandidateNode entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1);
382 checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
383 final DataTreeCandidateNode entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2);
384 checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
385 final DataTreeCandidateNode entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3);
386 checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
390 private static void checkChange(final NormalizedNode<?, ?> expectedBefore,
391 final NormalizedNode<?, ?> expectedAfter,
392 final ModificationType expectedMod,
393 final DataTreeCandidateNode candidateNode) {
394 if (expectedBefore != null) {
395 assertTrue(candidateNode.getDataBefore().isPresent());
396 assertEquals(expectedBefore, candidateNode.getDataBefore().get());
398 assertFalse(candidateNode.getDataBefore().isPresent());
401 if (expectedAfter != null) {
402 assertTrue(candidateNode.getDataAfter().isPresent());
403 assertEquals(expectedAfter, candidateNode.getDataAfter().get());
405 assertFalse(candidateNode.getDataAfter().isPresent());
408 assertEquals(expectedMod, candidateNode.getModificationType());
411 private DOMDataTreeChangeService getDOMDataTreeChangeService() {
412 final DOMDataBrokerExtension extension = domBroker.getSupportedExtensions()
413 .get(DOMDataTreeChangeService.class);
414 if (extension == null) {
417 DOMDataTreeChangeService dataTreeChangeService = null;
418 if (extension instanceof DOMDataTreeChangeService) {
419 dataTreeChangeService = (DOMDataTreeChangeService) extension;
421 return dataTreeChangeService;
425 static class CommitExecutorService extends ForwardingExecutorService {
427 ExecutorService delegate;
429 public CommitExecutorService(final ExecutorService delegate) {
430 this.delegate = delegate;
434 protected ExecutorService delegate() {
439 static class TestDataTreeListener implements DOMDataTreeChangeListener {
441 private final List<Collection<DataTreeCandidate>> receivedChanges = new ArrayList<>();
442 private final CountDownLatch latch;
444 public TestDataTreeListener(final CountDownLatch latch) {
449 public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
450 receivedChanges.add(changes);
454 public List<Collection<DataTreeCandidate>> getReceivedChanges() {
455 return receivedChanges;