+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+
+@RunWith(Parameterized.class)
+public class DistributedDataStoreIntegrationTest {
+
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { DistributedDataStore.class }, { ClientBackedDataStore.class }
+ });
+ }
+
+ @Parameter
+ public Class<? extends AbstractDataStore> testParameter;
+
+ private ActorSystem system;
+
+ private final DatastoreContext.Builder datastoreContextBuilder = DatastoreContext.newBuilder()
+ .shardHeartbeatIntervalInMillis(100);
+
+ @Before
+ public void setUp() throws IOException {
+ InMemorySnapshotStore.clear();
+ InMemoryJournal.clear();
+ system = ActorSystem.create("cluster-test", ConfigFactory.load().getConfig("Member1"));
+ Address member1Address = AddressFromURIString.parse("akka://cluster-test@127.0.0.1:2558");
+ Cluster.get(system).join(member1Address);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ TestKit.shutdownActorSystem(system, Boolean.TRUE);
+ system = null;
+ }
+
+ protected ActorSystem getSystem() {
+ return system;
+ }
+
+ @Test
+ public void testWriteTransactionWithSingleShard() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, "transactionIntegrationTest", "test-1")) {
+
+ testWriteTransaction(dataStore, TestModel.TEST_PATH,
+ ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+
+ testWriteTransaction(dataStore, TestModel.OUTER_LIST_PATH,
+ ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build());
+ }
+ }
+ };
+ }
+
+ @Test
+ public void testWriteTransactionWithMultipleShards() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, "testWriteTransactionWithMultipleShards", "cars-1", "people-1")) {
+
+ DOMStoreWriteTransaction writeTx = dataStore.newWriteOnlyTransaction();
+ assertNotNull("newWriteOnlyTransaction returned null", writeTx);
+
+ writeTx.write(CarsModel.BASE_PATH, CarsModel.emptyContainer());
+ writeTx.write(PeopleModel.BASE_PATH, PeopleModel.emptyContainer());
+
+ doCommit(writeTx.ready());
+
+ writeTx = dataStore.newWriteOnlyTransaction();
+
+ writeTx.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode());
+ writeTx.write(PeopleModel.PERSON_LIST_PATH, PeopleModel.newPersonMapNode());
+
+ doCommit(writeTx.ready());
+
+ writeTx = dataStore.newWriteOnlyTransaction();
+
+ final MapEntryNode car = CarsModel.newCarEntry("optima", BigInteger.valueOf(20000));
+ final YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
+ writeTx.write(carPath, car);
+
+ final MapEntryNode person = PeopleModel.newPersonEntry("jack");
+ final YangInstanceIdentifier personPath = PeopleModel.newPersonPath("jack");
+ writeTx.write(personPath, person);
+
+ doCommit(writeTx.ready());
+
+ // Verify the data in the store
+ final DOMStoreReadTransaction readTx = dataStore.newReadOnlyTransaction();
+
+ Optional<NormalizedNode<?, ?>> optional = readTx.read(carPath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("Data node", car, optional.get());
+
+ optional = readTx.read(personPath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("Data node", person, optional.get());
+ }
+ }
+ };
+ }
+
+ @Test
+ public void testReadWriteTransactionWithSingleShard() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, "testReadWriteTransactionWithSingleShard", "test-1")) {
+
+ // 1. Create a read-write Tx
+ final DOMStoreReadWriteTransaction readWriteTx = dataStore.newReadWriteTransaction();
+ assertNotNull("newReadWriteTransaction returned null", readWriteTx);
+
+ // 2. Write some data
+ final YangInstanceIdentifier nodePath = TestModel.TEST_PATH;
+ final NormalizedNode<?, ?> nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME);
+ readWriteTx.write(nodePath, nodeToWrite);
+
+ // 3. Read the data from Tx
+ final Boolean exists = readWriteTx.exists(nodePath).checkedGet(5, TimeUnit.SECONDS);
+ assertEquals("exists", true, exists);
+
+ Optional<NormalizedNode<?, ?>> optional = readWriteTx.read(nodePath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("Data node", nodeToWrite, optional.get());
+
+ // 4. Ready the Tx for commit
+ final DOMStoreThreePhaseCommitCohort cohort = readWriteTx.ready();
+
+ // 5. Commit the Tx
+ doCommit(cohort);
+
+ // 6. Verify the data in the store
+ final DOMStoreReadTransaction readTx = dataStore.newReadOnlyTransaction();
+
+ optional = readTx.read(nodePath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("Data node", nodeToWrite, optional.get());
+ }
+ }
+ };
+ }
+
+ @Test
+ public void testReadWriteTransactionWithMultipleShards() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, "testReadWriteTransactionWithMultipleShards", "cars-1", "people-1")) {
+
+ DOMStoreReadWriteTransaction readWriteTx = dataStore.newReadWriteTransaction();
+ assertNotNull("newReadWriteTransaction returned null", readWriteTx);
+
+ readWriteTx.write(CarsModel.BASE_PATH, CarsModel.emptyContainer());
+ readWriteTx.write(PeopleModel.BASE_PATH, PeopleModel.emptyContainer());
+
+ doCommit(readWriteTx.ready());
+
+ readWriteTx = dataStore.newReadWriteTransaction();
+
+ readWriteTx.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode());
+ readWriteTx.write(PeopleModel.PERSON_LIST_PATH, PeopleModel.newPersonMapNode());
+
+ doCommit(readWriteTx.ready());
+
+ readWriteTx = dataStore.newReadWriteTransaction();
+
+ final MapEntryNode car = CarsModel.newCarEntry("optima", BigInteger.valueOf(20000));
+ final YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
+ readWriteTx.write(carPath, car);
+
+ final MapEntryNode person = PeopleModel.newPersonEntry("jack");
+ final YangInstanceIdentifier personPath = PeopleModel.newPersonPath("jack");
+ readWriteTx.write(personPath, person);
+
+ final Boolean exists = readWriteTx.exists(carPath).checkedGet(5, TimeUnit.SECONDS);
+ assertEquals("exists", true, exists);
+
+ Optional<NormalizedNode<?, ?>> optional = readWriteTx.read(carPath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("Data node", car, optional.get());
+
+ doCommit(readWriteTx.ready());
+
+ // Verify the data in the store
+ DOMStoreReadTransaction readTx = dataStore.newReadOnlyTransaction();
+
+ optional = readTx.read(carPath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("Data node", car, optional.get());
+
+ optional = readTx.read(personPath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("Data node", person, optional.get());
+
+ }
+ }
+ };
+ }
+
+ @Test
+ public void testSingleTransactionsWritesInQuickSuccession() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, "testSingleTransactionsWritesInQuickSuccession", "cars-1")) {
+
+ final DOMStoreTransactionChain txChain = dataStore.createTransactionChain();
+
+ DOMStoreWriteTransaction writeTx = txChain.newWriteOnlyTransaction();
+ writeTx.write(CarsModel.BASE_PATH, CarsModel.emptyContainer());
+ writeTx.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode());
+ doCommit(writeTx.ready());
+
+ writeTx = txChain.newWriteOnlyTransaction();
+
+ int numCars = 5;
+ for (int i = 0; i < numCars; i++) {
+ writeTx.write(CarsModel.newCarPath("car" + i),
+ CarsModel.newCarEntry("car" + i, BigInteger.valueOf(20000)));
+ }
+
+ doCommit(writeTx.ready());
+
+ final Optional<NormalizedNode<?, ?>> optional = txChain.newReadOnlyTransaction()
+ .read(CarsModel.CAR_LIST_PATH).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+ assertEquals("# cars", numCars, ((Collection<?>) optional.get().getValue()).size());
+ }
+ }
+ };
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ private void testTransactionWritesWithShardNotInitiallyReady(final String testName, final boolean writeOnly)
+ throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ final String shardName = "test-1";
+
+ // Setup the InMemoryJournal to block shard recovery to ensure
+ // the shard isn't
+ // initialized until we create and submit the write the Tx.
+ final String persistentID = String.format("member-1-shard-%s-%s", shardName, testName);
+ final CountDownLatch blockRecoveryLatch = new CountDownLatch(1);
+ InMemoryJournal.addBlockReadMessagesLatch(persistentID, blockRecoveryLatch);
+
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, testName, false, shardName)) {
+
+ // Create the write Tx
+ final DOMStoreWriteTransaction writeTx = writeOnly ? dataStore.newWriteOnlyTransaction()
+ : dataStore.newReadWriteTransaction();
+ assertNotNull("newReadWriteTransaction returned null", writeTx);
+
+ // Do some modification operations and ready the Tx on a
+ // separate thread.
+ final YangInstanceIdentifier listEntryPath = YangInstanceIdentifier
+ .builder(TestModel.OUTER_LIST_PATH)
+ .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build();
+
+ final AtomicReference<DOMStoreThreePhaseCommitCohort> txCohort = new AtomicReference<>();
+ final AtomicReference<Exception> caughtEx = new AtomicReference<>();
+ final CountDownLatch txReady = new CountDownLatch(1);
+ final Thread txThread = new Thread(() -> {
+ try {
+ writeTx.write(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+
+ writeTx.merge(TestModel.OUTER_LIST_PATH,
+ ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build());
+
+ writeTx.write(listEntryPath,
+ ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1));
+
+ writeTx.delete(listEntryPath);
+
+ txCohort.set(writeTx.ready());
+ } catch (Exception e) {
+ caughtEx.set(e);
+ } finally {
+ txReady.countDown();
+ }
+ });
+
+ txThread.start();
+
+ // Wait for the Tx operations to complete.
+ final boolean done = Uninterruptibles.awaitUninterruptibly(txReady, 5, TimeUnit.SECONDS);
+ if (caughtEx.get() != null) {
+ throw caughtEx.get();
+ }
+
+ assertEquals("Tx ready", true, done);
+
+ // At this point the Tx operations should be waiting for the
+ // shard to initialize so
+ // trigger the latch to let the shard recovery to continue.
+ blockRecoveryLatch.countDown();
+
+ // Wait for the Tx commit to complete.
+ doCommit(txCohort.get());
+
+ // Verify the data in the store
+ final DOMStoreReadTransaction readTx = dataStore.newReadOnlyTransaction();
+
+ Optional<NormalizedNode<?, ?>> optional = readTx.read(TestModel.TEST_PATH).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+
+ optional = readTx.read(TestModel.OUTER_LIST_PATH).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", true, optional.isPresent());
+
+ optional = readTx.read(listEntryPath).get(5, TimeUnit.SECONDS);
+ assertEquals("isPresent", false, optional.isPresent());
+ }
+ }
+ };
+ }
+
+ @Test
+ public void testWriteOnlyTransactionWithShardNotInitiallyReady() throws Exception {
+ datastoreContextBuilder.writeOnlyTransactionOptimizationsEnabled(true);
+ testTransactionWritesWithShardNotInitiallyReady("testWriteOnlyTransactionWithShardNotInitiallyReady", true);
+ }
+
+ @Test
+ public void testReadWriteTransactionWithShardNotInitiallyReady() throws Exception {
+ testTransactionWritesWithShardNotInitiallyReady("testReadWriteTransactionWithShardNotInitiallyReady", false);
+ }
+
+ @Test
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testTransactionReadsWithShardNotInitiallyReady() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ final String testName = "testTransactionReadsWithShardNotInitiallyReady";
+ final String shardName = "test-1";
+
+ // Setup the InMemoryJournal to block shard recovery to ensure
+ // the shard isn't
+ // initialized until we create the Tx.
+ final String persistentID = String.format("member-1-shard-%s-%s", shardName, testName);
+ final CountDownLatch blockRecoveryLatch = new CountDownLatch(1);
+ InMemoryJournal.addBlockReadMessagesLatch(persistentID, blockRecoveryLatch);
+
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, testName, false, shardName)) {
+
+ // Create the read-write Tx
+ final DOMStoreReadWriteTransaction readWriteTx = dataStore.newReadWriteTransaction();
+ assertNotNull("newReadWriteTransaction returned null", readWriteTx);
+
+ // Do some reads on the Tx on a separate thread.
+ final AtomicReference<CheckedFuture<Boolean, ReadFailedException>> txExistsFuture =
+ new AtomicReference<>();
+ final AtomicReference<CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>>
+ txReadFuture = new AtomicReference<>();
+ final AtomicReference<Exception> caughtEx = new AtomicReference<>();
+ final CountDownLatch txReadsDone = new CountDownLatch(1);
+ final Thread txThread = new Thread(() -> {
+ try {
+ readWriteTx.write(TestModel.TEST_PATH,
+ ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+
+ txExistsFuture.set(readWriteTx.exists(TestModel.TEST_PATH));
+
+ txReadFuture.set(readWriteTx.read(TestModel.TEST_PATH));
+ } catch (Exception e) {
+ caughtEx.set(e);
+ } finally {
+ txReadsDone.countDown();
+ }
+ });
+
+ txThread.start();
+
+ // Wait for the Tx operations to complete.
+ boolean done = Uninterruptibles.awaitUninterruptibly(txReadsDone, 5, TimeUnit.SECONDS);
+ if (caughtEx.get() != null) {
+ throw caughtEx.get();
+ }
+
+ assertEquals("Tx reads done", true, done);
+
+ // At this point the Tx operations should be waiting for the
+ // shard to initialize so
+ // trigger the latch to let the shard recovery to continue.
+ blockRecoveryLatch.countDown();
+
+ // Wait for the reads to complete and verify.
+ assertEquals("exists", true, txExistsFuture.get().checkedGet(5, TimeUnit.SECONDS));
+ assertEquals("read", true, txReadFuture.get().checkedGet(5, TimeUnit.SECONDS).isPresent());
+
+ readWriteTx.close();
+ }
+ }
+ };
+ }
+
+ @Test(expected = NotInitializedException.class)
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testTransactionCommitFailureWithShardNotInitialized() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ final String testName = "testTransactionCommitFailureWithShardNotInitialized";
+ final String shardName = "test-1";
+
+ // Set the shard initialization timeout low for the test.
+ datastoreContextBuilder.shardInitializationTimeout(300, TimeUnit.MILLISECONDS);
+
+ // Setup the InMemoryJournal to block shard recovery
+ // indefinitely.
+ final String persistentID = String.format("member-1-shard-%s-%s", shardName, testName);
+ final CountDownLatch blockRecoveryLatch = new CountDownLatch(1);
+ InMemoryJournal.addBlockReadMessagesLatch(persistentID, blockRecoveryLatch);
+
+ InMemoryJournal.addEntry(persistentID, 1, "Dummy data so akka will read from persistence");
+
+ final AbstractDataStore dataStore =
+ setupAbstractDataStore(testParameter, testName, false, shardName);
+
+ // Create the write Tx
+ final DOMStoreWriteTransaction writeTx = dataStore.newWriteOnlyTransaction();
+ assertNotNull("newReadWriteTransaction returned null", writeTx);
+
+ // Do some modifications and ready the Tx on a separate
+ // thread.
+ final AtomicReference<DOMStoreThreePhaseCommitCohort> txCohort = new AtomicReference<>();
+ final AtomicReference<Exception> caughtEx = new AtomicReference<>();
+ final CountDownLatch txReady = new CountDownLatch(1);
+ final Thread txThread = new Thread(() -> {
+ try {
+ writeTx.write(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+
+ txCohort.set(writeTx.ready());
+ } catch (Exception e) {
+ caughtEx.set(e);
+ } finally {
+ txReady.countDown();
+ }
+ });
+
+ txThread.start();
+
+ // Wait for the Tx operations to complete.
+ boolean done = Uninterruptibles.awaitUninterruptibly(txReady, 5, TimeUnit.SECONDS);
+ if (caughtEx.get() != null) {
+ throw caughtEx.get();
+ }
+
+ assertEquals("Tx ready", true, done);
+
+ // Wait for the commit to complete. Since the shard never
+ // initialized, the Tx should
+ // have timed out and throw an appropriate exception cause.
+ try {
+ txCohort.get().canCommit().get(5, TimeUnit.SECONDS);
+ fail("Expected NotInitializedException");
+ } catch (final Exception e) {
+ final Throwable root = Throwables.getRootCause(e);
+ Throwables.throwIfUnchecked(root);
+ throw new RuntimeException(root);
+ } finally {
+ blockRecoveryLatch.countDown();
+ }
+ }
+ };
+ }
+
+ @Test(expected = NotInitializedException.class)
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void testTransactionReadFailureWithShardNotInitialized() throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ final String testName = "testTransactionReadFailureWithShardNotInitialized";
+ final String shardName = "test-1";
+
+ // Set the shard initialization timeout low for the test.
+ datastoreContextBuilder.shardInitializationTimeout(300, TimeUnit.MILLISECONDS);
+
+ // Setup the InMemoryJournal to block shard recovery
+ // indefinitely.
+ final String persistentID = String.format("member-1-shard-%s-%s", shardName, testName);
+ final CountDownLatch blockRecoveryLatch = new CountDownLatch(1);
+ InMemoryJournal.addBlockReadMessagesLatch(persistentID, blockRecoveryLatch);
+
+ InMemoryJournal.addEntry(persistentID, 1, "Dummy data so akka will read from persistence");
+
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, testName, false, shardName)) {
+
+ // Create the read-write Tx
+ final DOMStoreReadWriteTransaction readWriteTx = dataStore.newReadWriteTransaction();
+ assertNotNull("newReadWriteTransaction returned null", readWriteTx);
+
+ // Do a read on the Tx on a separate thread.
+ final AtomicReference<CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>>
+ txReadFuture = new AtomicReference<>();
+ final AtomicReference<Exception> caughtEx = new AtomicReference<>();
+ final CountDownLatch txReadDone = new CountDownLatch(1);
+ final Thread txThread = new Thread(() -> {
+ try {
+ readWriteTx.write(TestModel.TEST_PATH,
+ ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+
+ txReadFuture.set(readWriteTx.read(TestModel.TEST_PATH));
+
+ readWriteTx.close();
+ } catch (Exception e) {
+ caughtEx.set(e);
+ } finally {
+ txReadDone.countDown();
+ }
+ });
+
+ txThread.start();
+
+ // Wait for the Tx operations to complete.
+ boolean done = Uninterruptibles.awaitUninterruptibly(txReadDone, 5, TimeUnit.SECONDS);
+ if (caughtEx.get() != null) {
+ throw caughtEx.get();
+ }
+
+ assertEquals("Tx read done", true, done);
+
+ // Wait for the read to complete. Since the shard never
+ // initialized, the Tx should
+ // have timed out and throw an appropriate exception cause.
+ try {
+ txReadFuture.get().checkedGet(5, TimeUnit.SECONDS);
+ fail("Expected NotInitializedException");
+ } catch (final ReadFailedException e) {
+ final Throwable root = Throwables.getRootCause(e);
+ Throwables.throwIfUnchecked(root);
+ throw new RuntimeException(root);
+ } finally {
+ blockRecoveryLatch.countDown();
+ }
+ }
+ }
+ };
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ private void testTransactionCommitFailureWithNoShardLeader(final boolean writeOnly, final String testName)
+ throws Exception {
+ new IntegrationTestKit(getSystem(), datastoreContextBuilder) {
+ {
+ final String shardName = "default";
+
+ // We don't want the shard to become the leader so prevent shard
+ // elections.
+ datastoreContextBuilder.customRaftPolicyImplementation(
+ "org.opendaylight.controller.cluster.raft.policy.DisableElectionsRaftPolicy");
+
+ // The ShardManager uses the election timeout for FindPrimary so
+ // reset it low so it will timeout quickly.
+ datastoreContextBuilder.shardHeartbeatIntervalInMillis(100).shardElectionTimeoutFactor(1)
+ .shardInitializationTimeout(200, TimeUnit.MILLISECONDS).frontendRequestTimeoutInSeconds(2);
+
+ try (AbstractDataStore dataStore = setupAbstractDataStore(
+ testParameter, testName, false, shardName)) {
+
+ final Object result = dataStore.getActorContext().executeOperation(
+ dataStore.getActorContext().getShardManager(), new FindLocalShard(shardName, true));
+ assertTrue("Expected LocalShardFound. Actual: " + result, result instanceof LocalShardFound);
+
+ // Create the write Tx.
+ DOMStoreWriteTransaction writeTxToClose = null;
+ try {
+ writeTxToClose = writeOnly ? dataStore.newWriteOnlyTransaction()
+ : dataStore.newReadWriteTransaction();
+ final DOMStoreWriteTransaction writeTx = writeTxToClose;
+ assertNotNull("newReadWriteTransaction returned null", writeTx);
+
+ // Do some modifications and ready the Tx on a separate
+ // thread.
+ final AtomicReference<DOMStoreThreePhaseCommitCohort> txCohort = new AtomicReference<>();
+ final AtomicReference<Exception> caughtEx = new AtomicReference<>();
+ final CountDownLatch txReady = new CountDownLatch(1);
+ final Thread txThread = new Thread(() -> {
+ try {
+ writeTx.write(TestModel.JUNK_PATH,
+ ImmutableNodes.containerNode(TestModel.JUNK_QNAME));
+
+ txCohort.set(writeTx.ready());
+ } catch (Exception e) {
+ caughtEx.set(e);
+ } finally {
+ txReady.countDown();
+ }
+ });
+
+ txThread.start();
+
+ // Wait for the Tx operations to complete.
+ boolean done = Uninterruptibles.awaitUninterruptibly(txReady, 5, TimeUnit.SECONDS);
+ if (caughtEx.get() != null) {
+ throw caughtEx.get();
+ }
+
+ assertEquals("Tx ready", true, done);