Merge "Bug 1430: Off-load notifications from single commit thread"
[controller.git] / opendaylight / md-sal / sal-inmemory-datastore / src / test / java / org / opendaylight / controller / md / sal / dom / store / impl / InMemoryDataStoreTest.java
1 /*
2  * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
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
15 import java.util.concurrent.ExecutionException;
16
17 import org.junit.Before;
18 import org.junit.Ignore;
19 import org.junit.Test;
20 import org.mockito.Mockito;
21 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
22 import org.opendaylight.controller.md.sal.dom.store.impl.SnapshotBackedWriteTransaction.TransactionReadyPrototype;
23 import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
24 import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
25 import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
26 import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransactionChain;
27 import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
33 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
34 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
35 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
36 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
37
38 import com.google.common.base.Optional;
39 import com.google.common.util.concurrent.ListenableFuture;
40 import com.google.common.util.concurrent.MoreExecutors;
41
42
43 public class InMemoryDataStoreTest {
44
45     private SchemaContext schemaContext;
46     private InMemoryDOMDataStore domStore;
47
48     @Before
49     public void setupStore() {
50         domStore = new InMemoryDOMDataStore("TEST", MoreExecutors.sameThreadExecutor(),
51                 MoreExecutors.sameThreadExecutor());
52         schemaContext = TestModel.createTestContext();
53         domStore.onGlobalContextUpdated(schemaContext);
54     }
55
56     @Test
57     public void testTransactionIsolation() throws InterruptedException, ExecutionException {
58
59         assertNotNull(domStore);
60
61         DOMStoreReadTransaction readTx = domStore.newReadOnlyTransaction();
62         assertNotNull(readTx);
63
64         DOMStoreReadWriteTransaction writeTx = domStore.newReadWriteTransaction();
65         assertNotNull(writeTx);
66
67         /**
68          * Writes /test in writeTx
69          */
70         NormalizedNode<?, ?> testNode = ImmutableNodes.containerNode(TestModel.TEST_QNAME);
71         writeTx.write(TestModel.TEST_PATH, testNode);
72
73         /**
74          * Reads /test from writeTx Read should return container.
75          */
76         ListenableFuture<Optional<NormalizedNode<?, ?>>> writeTxContainer = writeTx.read(TestModel.TEST_PATH);
77         assertEquals("read: isPresent", true, writeTxContainer.get().isPresent());
78         assertEquals("read: data", testNode, writeTxContainer.get().get());
79
80         /**
81          * Reads /test from readTx Read should return Absent.
82          */
83         ListenableFuture<Optional<NormalizedNode<?, ?>>> readTxContainer = readTx.read(TestModel.TEST_PATH);
84         assertEquals("read: isPresent", false, readTxContainer.get().isPresent());
85     }
86
87     @Test
88     public void testTransactionCommit() throws InterruptedException, ExecutionException {
89
90         DOMStoreReadWriteTransaction writeTx = domStore.newReadWriteTransaction();
91         assertNotNull(writeTx);
92
93         /**
94          * Writes /test in writeTx
95          */
96         NormalizedNode<?, ?> testNode = ImmutableNodes.containerNode(TestModel.TEST_QNAME);
97         writeTx.write(TestModel.TEST_PATH, testNode);
98
99         /**
100          * Reads /test from writeTx Read should return container.
101          */
102         ListenableFuture<Optional<NormalizedNode<?, ?>>> writeTxContainer = writeTx.read(TestModel.TEST_PATH);
103         assertEquals("read: isPresent", true, writeTxContainer.get().isPresent());
104         assertEquals("read: data", testNode, writeTxContainer.get().get());
105
106         DOMStoreThreePhaseCommitCohort cohort = writeTx.ready();
107
108         assertThreePhaseCommit(cohort);
109
110         Optional<NormalizedNode<?, ?>> afterCommitRead = domStore.newReadOnlyTransaction().read(TestModel.TEST_PATH)
111                 .get();
112         assertEquals("After commit read: isPresent", true, afterCommitRead.isPresent());
113         assertEquals("After commit read: data", testNode, afterCommitRead.get());
114     }
115
116     @Test
117     public void testDelete() throws Exception {
118
119         DOMStoreWriteTransaction writeTx = domStore.newWriteOnlyTransaction();
120         assertNotNull( writeTx );
121
122         // Write /test and commit
123
124         writeTx.write( TestModel.TEST_PATH, ImmutableNodes.containerNode( TestModel.TEST_QNAME ) );
125
126         assertThreePhaseCommit( writeTx.ready() );
127
128         Optional<NormalizedNode<?, ?>> afterCommitRead = domStore.newReadOnlyTransaction().
129                 read(TestModel.TEST_PATH ).get();
130         assertEquals( "After commit read: isPresent", true, afterCommitRead.isPresent() );
131
132         // Delete /test and verify
133
134         writeTx = domStore.newWriteOnlyTransaction();
135
136         writeTx.delete( TestModel.TEST_PATH );
137
138         assertThreePhaseCommit( writeTx.ready() );
139
140         afterCommitRead = domStore.newReadOnlyTransaction().
141                 read(TestModel.TEST_PATH ).get();
142         assertEquals( "After commit read: isPresent", false, afterCommitRead.isPresent() );
143     }
144
145     @Test
146     public void testMerge() throws Exception {
147
148         DOMStoreWriteTransaction writeTx = domStore.newWriteOnlyTransaction();
149         assertNotNull( writeTx );
150
151         ContainerNode containerNode = ImmutableContainerNodeBuilder.create()
152                 .withNodeIdentifier( new NodeIdentifier( TestModel.TEST_QNAME ) )
153                 .addChild( ImmutableNodes.mapNodeBuilder( TestModel.OUTER_LIST_QNAME )
154                         .addChild( ImmutableNodes.mapEntry( TestModel.OUTER_LIST_QNAME,
155                                                             TestModel.ID_QNAME, 1 ) ).build() ).build();
156
157         writeTx.merge( TestModel.TEST_PATH, containerNode );
158
159         assertThreePhaseCommit( writeTx.ready() );
160
161         Optional<NormalizedNode<?, ?>> afterCommitRead = domStore.newReadOnlyTransaction().
162                 read(TestModel.TEST_PATH ).get();
163         assertEquals( "After commit read: isPresent", true, afterCommitRead.isPresent() );
164         assertEquals( "After commit read: data", containerNode, afterCommitRead.get() );
165
166         // Merge a new list entry node
167
168         writeTx = domStore.newWriteOnlyTransaction();
169         assertNotNull( writeTx );
170
171         containerNode = ImmutableContainerNodeBuilder.create()
172                 .withNodeIdentifier( new NodeIdentifier( TestModel.TEST_QNAME ) )
173                 .addChild( ImmutableNodes.mapNodeBuilder( TestModel.OUTER_LIST_QNAME )
174                         .addChild( ImmutableNodes.mapEntry( TestModel.OUTER_LIST_QNAME,
175                                                             TestModel.ID_QNAME, 1 ) )
176                         .addChild( ImmutableNodes.mapEntry( TestModel.OUTER_LIST_QNAME,
177                                                             TestModel.ID_QNAME, 2 ) ).build() ).build();
178
179         writeTx.merge( TestModel.TEST_PATH, containerNode );
180
181         assertThreePhaseCommit( writeTx.ready() );
182
183         afterCommitRead = domStore.newReadOnlyTransaction().read(TestModel.TEST_PATH ).get();
184         assertEquals( "After commit read: isPresent", true, afterCommitRead.isPresent() );
185         assertEquals( "After commit read: data", containerNode, afterCommitRead.get() );
186     }
187
188     @Test(expected=ReadFailedException.class)
189     public void testReadWithReadOnlyTransactionClosed() throws Throwable {
190
191         DOMStoreReadTransaction readTx = domStore.newReadOnlyTransaction();
192         assertNotNull( readTx );
193
194         readTx.close();
195
196         doReadAndThrowEx( readTx );
197     }
198
199     @Test(expected=ReadFailedException.class)
200     public void testReadWithReadOnlyTransactionFailure() throws Throwable {
201
202         DataTreeSnapshot mockSnapshot = Mockito.mock( DataTreeSnapshot.class );
203         Mockito.doThrow( new RuntimeException( "mock ex" ) ).when( mockSnapshot )
204         .readNode( Mockito.any( YangInstanceIdentifier.class ) );
205
206         DOMStoreReadTransaction readTx = new SnapshotBackedReadTransaction( "1", mockSnapshot );
207
208         doReadAndThrowEx( readTx );
209     }
210
211     @Test(expected=ReadFailedException.class)
212     public void testReadWithReadWriteTransactionClosed() throws Throwable {
213
214         DOMStoreReadTransaction readTx = domStore.newReadWriteTransaction();
215         assertNotNull( readTx );
216
217         readTx.close();
218
219         doReadAndThrowEx( readTx );
220     }
221
222     @Test(expected=ReadFailedException.class)
223     public void testReadWithReadWriteTransactionFailure() throws Throwable {
224
225         DataTreeSnapshot mockSnapshot = Mockito.mock( DataTreeSnapshot.class );
226         DataTreeModification mockModification = Mockito.mock( DataTreeModification.class );
227         Mockito.doThrow( new RuntimeException( "mock ex" ) ).when( mockModification )
228         .readNode( Mockito.any( YangInstanceIdentifier.class ) );
229         Mockito.doReturn( mockModification ).when( mockSnapshot ).newModification();
230         TransactionReadyPrototype mockReady = Mockito.mock( TransactionReadyPrototype.class );
231         DOMStoreReadTransaction readTx = new SnapshotBackedReadWriteTransaction( "1", mockSnapshot, mockReady );
232
233         doReadAndThrowEx( readTx );
234     }
235
236     private void doReadAndThrowEx( DOMStoreReadTransaction readTx ) throws Throwable {
237
238         try {
239             readTx.read(TestModel.TEST_PATH).get();
240         } catch( ExecutionException e ) {
241             throw e.getCause();
242         }
243     }
244
245     @Test(expected=IllegalStateException.class)
246     public void testWriteWithTransactionReady() throws Exception {
247
248         DOMStoreWriteTransaction writeTx = domStore.newWriteOnlyTransaction();
249
250         writeTx.ready();
251
252         // Should throw ex
253         writeTx.write( TestModel.TEST_PATH, ImmutableNodes.containerNode( TestModel.TEST_QNAME ) );
254     }
255
256     @Test(expected=IllegalStateException.class)
257     public void testReadyWithTransactionAlreadyReady() throws Exception {
258
259         DOMStoreWriteTransaction writeTx = domStore.newWriteOnlyTransaction();
260
261         writeTx.ready();
262
263         // Should throw ex
264         writeTx.ready();
265     }
266
267     @Test
268     public void testTransactionAbort() throws InterruptedException, ExecutionException {
269
270         DOMStoreReadWriteTransaction writeTx = domStore.newReadWriteTransaction();
271         assertNotNull(writeTx);
272
273         assertTestContainerWrite(writeTx);
274
275         DOMStoreThreePhaseCommitCohort cohort = writeTx.ready();
276
277         assertTrue(cohort.canCommit().get().booleanValue());
278         cohort.preCommit().get();
279         cohort.abort().get();
280
281         Optional<NormalizedNode<?, ?>> afterCommitRead = domStore.newReadOnlyTransaction().read(TestModel.TEST_PATH)
282                 .get();
283         assertFalse(afterCommitRead.isPresent());
284     }
285
286     @Test
287     public void testTransactionChain() throws InterruptedException, ExecutionException {
288         DOMStoreTransactionChain txChain = domStore.createTransactionChain();
289         assertNotNull(txChain);
290
291         /**
292          * We alocate new read-write transaction and write /test
293          *
294          *
295          */
296         DOMStoreReadWriteTransaction firstTx = txChain.newReadWriteTransaction();
297         assertTestContainerWrite(firstTx);
298
299         /**
300          * First transaction is marked as ready, we are able to allocate chained
301          * transactions
302          */
303         DOMStoreThreePhaseCommitCohort firstWriteTxCohort = firstTx.ready();
304
305         /**
306          * We alocate chained transaction - read transaction, note first one is
307          * still not commited to datastore.
308          */
309         DOMStoreReadTransaction secondReadTx = txChain.newReadOnlyTransaction();
310
311         /**
312          *
313          * We test if we are able to read data from tx, read should not fail
314          * since we are using chained transaction.
315          *
316          *
317          */
318         assertTestContainerExists(secondReadTx);
319
320         /**
321          *
322          * We alocate next transaction, which is still based on first one, but
323          * is read-write.
324          *
325          */
326         DOMStoreReadWriteTransaction thirdDeleteTx = txChain.newReadWriteTransaction();
327
328         /**
329          * We test existence of /test in third transaction container should
330          * still be visible from first one (which is still uncommmited).
331          *
332          *
333          */
334         assertTestContainerExists(thirdDeleteTx);
335
336         /**
337          * We delete node in third transaction
338          */
339         thirdDeleteTx.delete(TestModel.TEST_PATH);
340
341         /**
342          * third transaction is sealed.
343          */
344         DOMStoreThreePhaseCommitCohort thirdDeleteTxCohort = thirdDeleteTx.ready();
345
346         /**
347          * We commit first transaction
348          *
349          */
350         assertThreePhaseCommit(firstWriteTxCohort);
351
352         // Alocates store transacion
353         DOMStoreReadTransaction storeReadTx = domStore.newReadOnlyTransaction();
354         /**
355          * We verify transaction is commited to store, container should exists
356          * in datastore.
357          */
358         assertTestContainerExists(storeReadTx);
359         /**
360          * We commit third transaction
361          *
362          */
363         assertThreePhaseCommit(thirdDeleteTxCohort);
364     }
365
366     @Test
367     @Ignore
368     public void testTransactionConflict() throws InterruptedException, ExecutionException {
369         DOMStoreReadWriteTransaction txOne = domStore.newReadWriteTransaction();
370         DOMStoreReadWriteTransaction txTwo = domStore.newReadWriteTransaction();
371         assertTestContainerWrite(txOne);
372         assertTestContainerWrite(txTwo);
373
374         /**
375          * Commits transaction
376          */
377         assertThreePhaseCommit(txOne.ready());
378
379         /**
380          * Asserts that txTwo could not be commited
381          */
382         assertFalse(txTwo.ready().canCommit().get());
383     }
384
385     private static void assertThreePhaseCommit(final DOMStoreThreePhaseCommitCohort cohort)
386             throws InterruptedException, ExecutionException {
387         assertTrue(cohort.canCommit().get().booleanValue());
388         cohort.preCommit().get();
389         cohort.commit().get();
390     }
391
392     private static Optional<NormalizedNode<?, ?>> assertTestContainerWrite(final DOMStoreReadWriteTransaction writeTx)
393             throws InterruptedException, ExecutionException {
394         /**
395          *
396          * Writes /test in writeTx
397          *
398          */
399         writeTx.write(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
400
401         return assertTestContainerExists(writeTx);
402     }
403
404     /**
405      * Reads /test from readTx Read should return container.
406      */
407     private static Optional<NormalizedNode<?, ?>> assertTestContainerExists(final DOMStoreReadTransaction readTx)
408             throws InterruptedException, ExecutionException {
409
410         ListenableFuture<Optional<NormalizedNode<?, ?>>> writeTxContainer = readTx.read(TestModel.TEST_PATH);
411         assertTrue(writeTxContainer.get().isPresent());
412         return writeTxContainer.get();
413     }
414
415 }