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