DOMDataTreeChangeListener.onInitialData() is mandatory
[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 package org.opendaylight.mdsal.dom.broker;
9
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;
16
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.broker.util.TestModel;
39 import org.opendaylight.mdsal.dom.spi.store.DOMStore;
40 import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataStore;
41 import org.opendaylight.yangtools.concepts.ListenerRegistration;
42 import org.opendaylight.yangtools.util.concurrent.DeadlockDetectingListeningExecutorService;
43 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
46 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
50 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
52 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
53 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
54
55 public class DOMDataTreeListenerTest extends AbstractDatastoreTest {
56
57     private AbstractDOMDataBroker domBroker;
58     private ListeningExecutorService executor;
59     private ExecutorService futureExecutor;
60     private CommitExecutorService commitExecutor;
61
62     private static final MapNode OUTER_LIST = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
63             .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1))
64             .build();
65
66     private static final MapNode OUTER_LIST_2 = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
67             .withChild(ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2))
68             .build();
69
70     private static final NormalizedNode TEST_CONTAINER = Builders.containerBuilder()
71             .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
72             .withChild(OUTER_LIST)
73             .build();
74
75     private static final NormalizedNode TEST_CONTAINER_2 = Builders.containerBuilder()
76             .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME))
77             .withChild(OUTER_LIST_2)
78             .build();
79
80     private static final DOMDataTreeIdentifier ROOT_DATA_TREE_ID = new DOMDataTreeIdentifier(
81             LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
82
83     private static final DOMDataTreeIdentifier OUTER_LIST_DATA_TREE_ID = new DOMDataTreeIdentifier(
84             LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH);
85
86     @Before
87     public void setupStore() {
88         final InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER",
89                 MoreExecutors.newDirectExecutorService());
90         final InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG",
91                 MoreExecutors.newDirectExecutorService());
92
93         operStore.onModelContextUpdated(SCHEMA_CONTEXT);
94         configStore.onModelContextUpdated(SCHEMA_CONTEXT);
95
96         final ImmutableMap<LogicalDatastoreType, DOMStore> stores = ImmutableMap.<LogicalDatastoreType,
97                 DOMStore>builder()
98                 .put(CONFIGURATION, configStore)
99                 .put(OPERATIONAL, operStore)
100                 .build();
101
102         commitExecutor = new CommitExecutorService(Executors.newSingleThreadExecutor());
103         futureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(1, 5, "FCB",
104                 DOMDataTreeListenerTest.class);
105         executor = new DeadlockDetectingListeningExecutorService(commitExecutor,
106                 TransactionCommitDeadlockException.DEADLOCK_EXCEPTION_SUPPLIER, futureExecutor);
107         domBroker = new SerializedDOMDataBroker(stores, executor);
108     }
109
110     @After
111     public void tearDown() {
112         if (executor != null) {
113             executor.shutdownNow();
114         }
115
116         if (futureExecutor != null) {
117             futureExecutor.shutdownNow();
118         }
119     }
120
121     @Test
122     public void writeContainerEmptyTreeTest() throws InterruptedException {
123         final CountDownLatch latch = new CountDownLatch(1);
124
125         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
126         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
127                 dataTreeChangeService);
128
129         final TestDataTreeListener listener = new TestDataTreeListener(latch);
130         final ListenerRegistration<TestDataTreeListener> listenerReg =
131                 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
132
133         final DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
134         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
135         writeTx.commit();
136
137         latch.await(5, TimeUnit.SECONDS);
138
139         assertEquals(1, listener.getReceivedChanges().size());
140         final Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
141         assertEquals(1, changes.size());
142
143         final DataTreeCandidate candidate = changes.iterator().next();
144         assertNotNull(candidate);
145         final DataTreeCandidateNode candidateRoot = candidate.getRootNode();
146         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
147         listenerReg.close();
148     }
149
150     @Test
151     public void replaceContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
152         final CountDownLatch latch = new CountDownLatch(2);
153
154         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
155         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
156                 dataTreeChangeService);
157
158         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
159         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
160         writeTx.commit().get();
161
162         final TestDataTreeListener listener = new TestDataTreeListener(latch);
163         final ListenerRegistration<TestDataTreeListener> listenerReg =
164                 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
165         writeTx = domBroker.newWriteOnlyTransaction();
166         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
167         writeTx.commit();
168
169         latch.await(5, TimeUnit.SECONDS);
170
171         assertEquals(2, listener.getReceivedChanges().size());
172         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
173         assertEquals(1, changes.size());
174
175         DataTreeCandidate candidate = changes.iterator().next();
176         assertNotNull(candidate);
177         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
178         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
179
180         changes = listener.getReceivedChanges().get(1);
181         assertEquals(1, changes.size());
182
183         candidate = changes.iterator().next();
184         assertNotNull(candidate);
185         candidateRoot = candidate.getRootNode();
186         checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.WRITE, candidateRoot);
187         listenerReg.close();
188     }
189
190     @Test
191     public void deleteContainerContainerInTreeTest() throws ExecutionException, InterruptedException {
192         final CountDownLatch latch = new CountDownLatch(2);
193
194         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
195         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
196                 dataTreeChangeService);
197
198         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
199         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
200         writeTx.commit().get();
201
202         final TestDataTreeListener listener = new TestDataTreeListener(latch);
203         final ListenerRegistration<TestDataTreeListener> listenerReg =
204                 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
205
206         writeTx = domBroker.newWriteOnlyTransaction();
207         writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH);
208         writeTx.commit();
209
210         latch.await(5, TimeUnit.SECONDS);
211
212         assertEquals(2, listener.getReceivedChanges().size());
213         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
214         assertEquals(1, changes.size());
215
216         DataTreeCandidate candidate = changes.iterator().next();
217         assertNotNull(candidate);
218         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
219         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
220
221         changes = listener.getReceivedChanges().get(1);
222         assertEquals(1, changes.size());
223
224         candidate = changes.iterator().next();
225         assertNotNull(candidate);
226         candidateRoot = candidate.getRootNode();
227         checkChange(TEST_CONTAINER, null, ModificationType.DELETE, candidateRoot);
228         listenerReg.close();
229     }
230
231     @Test
232     public void replaceChildListContainerInTreeTest() throws ExecutionException, InterruptedException {
233         final CountDownLatch latch = new CountDownLatch(2);
234
235         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
236         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
237                 dataTreeChangeService);
238
239         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
240         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
241         writeTx.commit().get();
242
243         final TestDataTreeListener listener = new TestDataTreeListener(latch);
244         final ListenerRegistration<TestDataTreeListener> listenerReg =
245                 dataTreeChangeService.registerDataTreeChangeListener(ROOT_DATA_TREE_ID, listener);
246
247         writeTx = domBroker.newWriteOnlyTransaction();
248         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH, OUTER_LIST_2);
249         writeTx.commit();
250
251         latch.await(5, TimeUnit.SECONDS);
252
253         assertEquals(2, listener.getReceivedChanges().size());
254         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
255         assertEquals(1, changes.size());
256
257         DataTreeCandidate candidate = changes.iterator().next();
258         assertNotNull(candidate);
259         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
260         checkChange(null, TEST_CONTAINER, ModificationType.WRITE, candidateRoot);
261
262         changes = listener.getReceivedChanges().get(1);
263         assertEquals(1, changes.size());
264
265         candidate = changes.iterator().next();
266         assertNotNull(candidate);
267         candidateRoot = candidate.getRootNode();
268         checkChange(TEST_CONTAINER, TEST_CONTAINER_2, ModificationType.SUBTREE_MODIFIED, candidateRoot);
269         final DataTreeCandidateNode modifiedChild = candidateRoot.getModifiedChild(
270                 new YangInstanceIdentifier.NodeIdentifier(TestModel.OUTER_LIST_QNAME)).get();
271         checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, modifiedChild);
272         listenerReg.close();
273     }
274
275     @Test
276     public void rootModificationChildListenerTest() throws ExecutionException, InterruptedException {
277         final CountDownLatch latch = new CountDownLatch(2);
278
279         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
280         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
281                 dataTreeChangeService);
282
283         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
284         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
285         writeTx.commit().get();
286
287         final TestDataTreeListener listener = new TestDataTreeListener(latch);
288         final ListenerRegistration<TestDataTreeListener> listenerReg =
289                 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
290
291         writeTx = domBroker.newWriteOnlyTransaction();
292         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER_2);
293         writeTx.commit().get();
294
295         latch.await(1, TimeUnit.SECONDS);
296
297         assertEquals(2, listener.getReceivedChanges().size());
298         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
299         assertEquals(1, changes.size());
300
301         DataTreeCandidate candidate = changes.iterator().next();
302         assertNotNull(candidate);
303         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
304         checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
305
306         changes = listener.getReceivedChanges().get(1);
307         assertEquals(1, changes.size());
308
309         candidate = changes.iterator().next();
310         assertNotNull(candidate);
311         candidateRoot = candidate.getRootNode();
312         checkChange(OUTER_LIST, OUTER_LIST_2, ModificationType.WRITE, candidateRoot);
313         listenerReg.close();
314     }
315
316     @Test
317     public void listEntryChangeNonRootRegistrationTest() throws ExecutionException, InterruptedException {
318         final CountDownLatch latch = new CountDownLatch(2);
319
320         final DOMDataTreeChangeService dataTreeChangeService = getDOMDataTreeChangeService();
321         assertNotNull("DOMDataTreeChangeService not found, cannot continue with test!",
322                 dataTreeChangeService);
323
324         DOMDataTreeWriteTransaction writeTx = domBroker.newWriteOnlyTransaction();
325         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.TEST_PATH, TEST_CONTAINER);
326         writeTx.commit().get();
327
328         final TestDataTreeListener listener = new TestDataTreeListener(latch);
329         final ListenerRegistration<TestDataTreeListener> listenerReg =
330                 dataTreeChangeService.registerDataTreeChangeListener(OUTER_LIST_DATA_TREE_ID, listener);
331
332         final NodeIdentifierWithPredicates outerListEntryId1 =
333                 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1);
334         final NodeIdentifierWithPredicates outerListEntryId2 =
335                 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2);
336         final NodeIdentifierWithPredicates outerListEntryId3 =
337                 NodeIdentifierWithPredicates.of(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3);
338
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);
342
343         final MapNode listAfter = ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
344                 .withChild(outerListEntry2)
345                 .withChild(outerListEntry3)
346                 .build();
347
348         writeTx = domBroker.newWriteOnlyTransaction();
349         writeTx.delete(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId1));
350         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId2),
351                 outerListEntry2);
352         writeTx.put(LogicalDatastoreType.CONFIGURATION, TestModel.OUTER_LIST_PATH.node(outerListEntryId3),
353                 outerListEntry3);
354         writeTx.commit();
355
356         latch.await(5, TimeUnit.SECONDS);
357
358         assertEquals(2, listener.getReceivedChanges().size());
359         Collection<DataTreeCandidate> changes = listener.getReceivedChanges().get(0);
360         assertEquals(1, changes.size());
361
362         DataTreeCandidate candidate = changes.iterator().next();
363         assertNotNull(candidate);
364         DataTreeCandidateNode candidateRoot = candidate.getRootNode();
365         checkChange(null, OUTER_LIST, ModificationType.WRITE, candidateRoot);
366
367         changes = listener.getReceivedChanges().get(1);
368         assertEquals(1, changes.size());
369
370         candidate = changes.iterator().next();
371         assertNotNull(candidate);
372         candidateRoot = candidate.getRootNode();
373         checkChange(OUTER_LIST, listAfter, ModificationType.SUBTREE_MODIFIED, candidateRoot);
374         final DataTreeCandidateNode entry1Canditate = candidateRoot.getModifiedChild(outerListEntryId1).get();
375         checkChange(outerListEntry1, null, ModificationType.DELETE, entry1Canditate);
376         final DataTreeCandidateNode entry2Canditate = candidateRoot.getModifiedChild(outerListEntryId2).get();
377         checkChange(null, outerListEntry2, ModificationType.WRITE, entry2Canditate);
378         final DataTreeCandidateNode entry3Canditate = candidateRoot.getModifiedChild(outerListEntryId3).get();
379         checkChange(null, outerListEntry3, ModificationType.WRITE, entry3Canditate);
380         listenerReg.close();
381     }
382
383     private static void checkChange(final NormalizedNode expectedBefore, final NormalizedNode expectedAfter,
384                                     final ModificationType expectedMod, final DataTreeCandidateNode candidateNode) {
385         if (expectedBefore != null) {
386             assertTrue(candidateNode.getDataBefore().isPresent());
387             assertEquals(expectedBefore, candidateNode.getDataBefore().get());
388         } else {
389             assertFalse(candidateNode.getDataBefore().isPresent());
390         }
391
392         if (expectedAfter != null) {
393             assertTrue(candidateNode.getDataAfter().isPresent());
394             assertEquals(expectedAfter, candidateNode.getDataAfter().get());
395         } else {
396             assertFalse(candidateNode.getDataAfter().isPresent());
397         }
398
399         assertEquals(expectedMod, candidateNode.getModificationType());
400     }
401
402     private DOMDataTreeChangeService getDOMDataTreeChangeService() {
403         return domBroker.getExtensions().getInstance(DOMDataTreeChangeService.class);
404     }
405
406     static class CommitExecutorService extends ForwardingExecutorService {
407
408         ExecutorService delegate;
409
410         CommitExecutorService(final ExecutorService delegate) {
411             this.delegate = delegate;
412         }
413
414         @Override
415         protected ExecutorService delegate() {
416             return delegate;
417         }
418     }
419
420     static class TestDataTreeListener implements DOMDataTreeChangeListener {
421         private final List<Collection<DataTreeCandidate>> receivedChanges = new ArrayList<>();
422         private final CountDownLatch latch;
423
424         TestDataTreeListener(final CountDownLatch latch) {
425             this.latch = latch;
426         }
427
428         @Override
429         public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
430             receivedChanges.add(changes);
431             latch.countDown();
432         }
433
434         @Override
435         public void onInitialData() {
436             // noop
437         }
438
439         List<Collection<DataTreeCandidate>> getReceivedChanges() {
440             return receivedChanges;
441         }
442     }
443 }