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