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