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