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