6e734567aadc35c529ae01ae977c497bb4a167e7
[mdsal.git] / dom / mdsal-dom-broker / src / test / java / org / opendaylight / mdsal / dom / broker / DOMDataTreeListenerTest.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.mdsal.dom.broker;
10
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;
17
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.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.mdsal.common.api.LogicalDatastoreType;
34 import org.opendaylight.mdsal.common.api.TransactionCommitDeadlockException;
35 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
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;
58
59 public class DOMDataTreeListenerTest {
60
61     private SchemaContext schemaContext;
62     private AbstractDOMDataBroker domBroker;
63     private ListeningExecutorService executor;
64     private ExecutorService futureExecutor;
65     private CommitExecutorService commitExecutor;
66
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))
70             .build();
71
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))
75             .build();
76
77     private static final NormalizedNode<?, ?> TEST_CONTAINER = Builders.containerBuilder()
78             .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
79             .withChild(OUTER_LIST)
80             .build();
81
82     private static final NormalizedNode<?, ?> TEST_CONTAINER_2 = Builders.containerBuilder()
83             .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
84             .withChild(OUTER_LIST_2)
85             .build();
86
87     private static final DOMDataTreeIdentifier ROOT_DATA_TREE_ID = new DOMDataTreeIdentifier(
88             LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
89
90     private static final DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID = new DOMDataTreeIdentifier(
91             LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
92
93     @Before
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();
100
101         operStore.onGlobalContextUpdated(schemaContext);
102         configStore.onGlobalContextUpdated(schemaContext);
103
104         final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType, DOMStore>builder()
105                 .put(CONFIGURATION, configStore)
106                 .put(OPERATIONAL, operStore)
107                 .build();
108
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);
114     }
115
116     @After
117     public void tearDown() {
118         if (executor != null) {
119             executor.shutdownNow();
120         }
121
122         if (futureExecutor != null) {
123             futureExecutor.shutdownNow();
124         }
125     }
126
127     @Test
128     public void writeContainerEmptyTreeTest() throws InterruptedException {
129         final CountDownLatch latch = new CountDownLatch(1);
130
131         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
132         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
133                 dataTreeChangeService);
134
135         final TestDataTreeListener listener = new TestDataTreeListener(latch);
136         final ListenerRegistration<TestDataTreeListener> listenerReg =
137                 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
138
139         final DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
140         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
141         writeTx.submit();
142
143         latch.await(5, TimeUnit.SECONDS);
144
145         assertEquals(1, listener.getReceivedChanges().size());
146         final Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
147         assertEquals(1, changes.size());
148
149         final DataTreeCandidate candidate = changes.iterator().next();
150         assertNotNull(candidate);
151         final DataTreeCandidateNode candidateRoot = candidate.getRootNode();
152         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
153         listenerReg.close();
154     }
155
156     @Test
157     public void replaceContainerContainerInTreeTest() throws InterruptedException, TransactionCommitFailedException {
158         final CountDownLatch latch = new CountDownLatch(2);
159
160         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
161         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
162                 dataTreeChangeService);
163
164         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
165         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
166         writeTx.submit().checkedGet();
167
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);
173         writeTx.submit();
174
175         latch.await(5, TimeUnit.SECONDS);
176
177         assertEquals(2, listener.getReceivedChanges().size());
178         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
179         assertEquals(1, changes.size());
180
181         DataTreeCandidate candidate = changes.iterator().next();
182         assertNotNull(candidate);
183         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
184         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
185
186         changes = listener.getReceivedChanges().get(1);
187         assertEquals(1, changes.size());
188
189         candidate = changes.iterator().next();
190         assertNotNull(candidate);
191         candidateRoot = candidate.getRootNode();
192         checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
193         listenerReg.close();
194     }
195
196     @Test
197     public void deleteContainerContainerInTreeTest() throws InterruptedException, TransactionCommitFailedException {
198         final CountDownLatch latch = new CountDownLatch(2);
199
200         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
201         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
202                 dataTreeChangeService);
203
204         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
205         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
206         writeTx.submit().checkedGet();
207
208         final TestDataTreeListener listener = new TestDataTreeListener(latch);
209         final ListenerRegistration<TestDataTreeListener> listenerReg =
210                 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
211
212         writeTx = domBroker.newWriteOnlyTransaction();
213         writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
214         writeTx.submit();
215
216         latch.await(5, TimeUnit.SECONDS);
217
218         assertEquals(2, listener.getReceivedChanges().size());
219         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
220         assertEquals(1, changes.size());
221
222         DataTreeCandidate candidate = changes.iterator().next();
223         assertNotNull(candidate);
224         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
225         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
226
227         changes = listener.getReceivedChanges().get(1);
228         assertEquals(1, changes.size());
229
230         candidate = changes.iterator().next();
231         assertNotNull(candidate);
232         candidateRoot = candidate.getRootNode();
233         checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
234         listenerReg.close();
235     }
236
237     @Test
238     public void replaceChildListContainerInTreeTest() throws InterruptedException, TransactionCommitFailedException {
239         final CountDownLatch latch = new CountDownLatch(2);
240
241         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
242         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
243                 dataTreeChangeService);
244
245         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
246         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
247         writeTx.submit().checkedGet();
248
249         final TestDataTreeListener listener = new TestDataTreeListener(latch);
250         final ListenerRegistration<TestDataTreeListener> listenerReg =
251                 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
252
253         writeTx = domBroker.newWriteOnlyTransaction();
254         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
255         writeTx.submit();
256
257         latch.await(5, TimeUnit.SECONDS);
258
259         assertEquals(2, listener.getReceivedChanges().size());
260         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
261         assertEquals(1, changes.size());
262
263         DataTreeCandidate candidate = changes.iterator().next();
264         assertNotNull(candidate);
265         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
266         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
267
268         changes = listener.getReceivedChanges().get(1);
269         assertEquals(1, changes.size());
270
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);
279         listenerReg.close();
280     }
281
282     @Test
283     public void rootModificationChildListenerTest() throws InterruptedException, TransactionCommitFailedException {
284         final CountDownLatch latch = new CountDownLatch(2);
285
286         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
287         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
288                 dataTreeChangeService);
289
290         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
291         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
292         writeTx.submit().checkedGet();
293
294         final TestDataTreeListener listener = new TestDataTreeListener(latch);
295         final ListenerRegistration<TestDataTreeListener> listenerReg =
296                 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
297
298         writeTx = domBroker.newWriteOnlyTransaction();
299         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
300         writeTx.submit().checkedGet();
301
302         latch.await(1, TimeUnit.SECONDS);
303
304         assertEquals(2, listener.getReceivedChanges().size());
305         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
306         assertEquals(1, changes.size());
307
308         DataTreeCandidate candidate = changes.iterator().next();
309         assertNotNull(candidate);
310         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
311         checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
312
313         changes = listener.getReceivedChanges().get(1);
314         assertEquals(1, changes.size());
315
316         candidate = changes.iterator().next();
317         assertNotNull(candidate);
318         candidateRoot = candidate.getRootNode();
319         checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
320         listenerReg.close();
321     }
322
323     @Test
324     public void listEntryChangeNonRootRegistrationTest() throws InterruptedException, TransactionCommitFailedException {
325         final CountDownLatch latch = new CountDownLatch(2);
326
327         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
328         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
329                 dataTreeChangeService);
330
331         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
332         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
333         writeTx.submit().checkedGet();
334
335         final TestDataTreeListener listener = new TestDataTreeListener(latch);
336         final ListenerRegistration<TestDataTreeListener> listenerReg =
337                 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
338
339         final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId1 =
340                 new YangInstanceIdentifier.NodeIdentifierWithPredicates(
341                         TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
342         final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId2 =
343                 new YangInstanceIdentifier.NodeIdentifierWithPredicates(
344                         TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
345         final YangInstanceIdentifier.NodeIdentifierWithPredicates outerListEntryId3 =
346                 new YangInstanceIdentifier.NodeIdentifierWithPredicates(
347                         TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
348
349         final MapEntryNode outerListEntry1 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
350         final MapEntryNode outerListEntry2 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
351         final MapEntryNode outerListEntry3 = ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
352
353         final MapNode listAfter = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
354                 .withChild(outerListEntry2)
355                 .withChild(outerListEntry3)
356                 .build();
357
358         writeTx = domBroker.newWriteOnlyTransaction();
359         writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
360         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
361                 outerListEntry2);
362         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
363                 outerListEntry3);
364         writeTx.submit();
365
366         latch.await(5, TimeUnit.SECONDS);
367
368         assertEquals(2, listener.getReceivedChanges().size());
369         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
370         assertEquals(1, changes.size());
371
372         DataTreeCandidate candidate = changes.iterator().next();
373         assertNotNull(candidate);
374         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
375         checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
376
377         changes = listener.getReceivedChanges().get(1);
378         assertEquals(1, changes.size());
379
380         candidate = changes.iterator().next();
381         assertNotNull(candidate);
382         candidateRoot = candidate.getRootNode();
383         checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
384         final DataTreeCandidateNode entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1);
385         checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
386         final DataTreeCandidateNode entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2);
387         checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
388         final DataTreeCandidateNode entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3);
389         checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
390         listenerReg.close();
391     }
392
393     private static void checkChange(final NormalizedNode<?, ?> expectedBefore,
394                                     final NormalizedNode<?, ?> expectedAfter,
395                                     final ModificationType expectedMod,
396                                     final DataTreeCandidateNode candidateNode) {
397         if (expectedBefore != null) {
398             assertTrue(candidateNode.getDataBefore().isPresent());
399             assertEquals(expectedBefore, candidateNode.getDataBefore().get());
400         } else {
401             assertFalse(candidateNode.getDataBefore().isPresent());
402         }
403
404         if (expectedAfter != null) {
405             assertTrue(candidateNode.getDataAfter().isPresent());
406             assertEquals(expectedAfter, candidateNode.getDataAfter().get());
407         } else {
408             assertFalse(candidateNode.getDataAfter().isPresent());
409         }
410
411         assertEquals(expectedMod, candidateNode.getModificationType());
412     }
413
414     private DOMDataTreeChangeService getDOMDataTreeChangeService() {
415         final DOMDataBrokerExtension extension = domBroker.getSupportedExtensions()
416                 .get(DOMDataTreeChangeService.class);
417         if (extension == null) {
418             return null;
419         }
420         DOMDataTreeChangeService dataTreeChangeService = null;
421         if (extension instanceof DOMDataTreeChangeService) {
422             dataTreeChangeService = (DOMDataTreeChangeService) extension;
423         }
424         return dataTreeChangeService;
425     }
426
427     static class CommitExecutorService extends ForwardingExecutorService {
428
429         ExecutorService delegate;
430
431         public CommitExecutorService(final ExecutorService delegate) {
432             this.delegate = delegate;
433         }
434
435         @Override
436         protected ExecutorService delegate() {
437             return delegate;
438         }
439     }
440
441     static class TestDataTreeListener implements DOMDataTreeChangeListener {
442
443         private final List<Collection<DataTreeCandidate>> receivedChanges = new ArrayList<>();
444         private final CountDownLatch latch;
445
446         public TestDataTreeListener(final CountDownLatch latch) {
447             this.latch = latch;
448         }
449
450         @Override
451         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
452             receivedChanges.add(changes);
453             latch.countDown();
454         }
455
456         public List<Collection<DataTreeCandidate>> getReceivedChanges() {
457             return receivedChanges;
458         }
459     }
460 }