+ @Test
+ public void testListenerNotifiedOnApplySnapshot() throws Exception {
+ immediatePayloadReplication(shardDataTree, mockShard);
+
+ DOMDataTreeChangeListener listener = mock(DOMDataTreeChangeListener.class);
+ shardDataTree.registerTreeChangeListener(CarsModel.CAR_LIST_PATH.node(CarsModel.CAR_QNAME), listener,
+ Optional.empty(), noop -> { });
+
+ addCar(shardDataTree, "optima");
+
+ verifyOnDataTreeChanged(listener, dtc -> {
+ assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
+ assertEquals("getRootPath", CarsModel.newCarPath("optima"), dtc.getRootPath());
+ });
+
+ addCar(shardDataTree, "sportage");
+
+ verifyOnDataTreeChanged(listener, dtc -> {
+ assertEquals("getModificationType", ModificationType.WRITE, dtc.getRootNode().getModificationType());
+ assertEquals("getRootPath", CarsModel.newCarPath("sportage"), dtc.getRootPath());
+ });
+
+ ShardDataTree newDataTree = new ShardDataTree(mockShard, fullSchema, TreeType.OPERATIONAL);
+ immediatePayloadReplication(newDataTree, mockShard);
+ addCar(newDataTree, "optima");
+ addCar(newDataTree, "murano");
+
+ shardDataTree.applySnapshot(newDataTree.takeStateSnapshot());
+
+ Map<YangInstanceIdentifier, ModificationType> expChanges = new HashMap<>();
+ expChanges.put(CarsModel.newCarPath("optima"), ModificationType.WRITE);
+ expChanges.put(CarsModel.newCarPath("murano"), ModificationType.WRITE);
+ expChanges.put(CarsModel.newCarPath("sportage"), ModificationType.DELETE);
+ verifyOnDataTreeChanged(listener, dtc -> {
+ ModificationType expType = expChanges.remove(dtc.getRootPath());
+ assertNotNull("Got unexpected change for " + dtc.getRootPath(), expType);
+ assertEquals("getModificationType", expType, dtc.getRootNode().getModificationType());
+ });
+
+ if (!expChanges.isEmpty()) {
+ fail("Missing change notifications: " + expChanges);
+ }
+ }
+
+ @Test
+ public void testPipelinedTransactionsWithCoordinatedCommits() throws Exception {
+ final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
+
+ final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
+
+ NormalizedNode peopleNode = PeopleModel.create();
+ final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(PeopleModel.BASE_PATH, peopleNode));
+
+ YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
+ MapEntryNode carNode = CarsModel.newCarEntry("optima", Uint64.valueOf(100));
+ final ShardDataTreeCohort cohort4 = newShardDataTreeCohort(snapshot -> snapshot.write(carPath, carNode));
+
+ immediateCanCommit(cohort1);
+ final FutureCallback<Void> canCommitCallback2 = coordinatedCanCommit(cohort2);
+ final FutureCallback<Void> canCommitCallback3 = coordinatedCanCommit(cohort3);
+ final FutureCallback<Void> canCommitCallback4 = coordinatedCanCommit(cohort4);
+
+ final FutureCallback<DataTreeCandidate> preCommitCallback1 = coordinatedPreCommit(cohort1);
+ verify(preCommitCallback1).onSuccess(cohort1.getCandidate());
+ verify(canCommitCallback2).onSuccess(null);
+
+ final FutureCallback<DataTreeCandidate> preCommitCallback2 = coordinatedPreCommit(cohort2);
+ verify(preCommitCallback2).onSuccess(cohort2.getCandidate());
+ verify(canCommitCallback3).onSuccess(null);
+
+ final FutureCallback<DataTreeCandidate> preCommitCallback3 = coordinatedPreCommit(cohort3);
+ verify(preCommitCallback3).onSuccess(cohort3.getCandidate());
+ verify(canCommitCallback4).onSuccess(null);
+
+ final FutureCallback<DataTreeCandidate> preCommitCallback4 = coordinatedPreCommit(cohort4);
+ verify(preCommitCallback4).onSuccess(cohort4.getCandidate());
+
+ final FutureCallback<UnsignedLong> commitCallback2 = coordinatedCommit(cohort2);
+ verify(mockShard, never()).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
+ anyBoolean());
+ verifyNoMoreInteractions(commitCallback2);
+
+ final FutureCallback<UnsignedLong> commitCallback4 = coordinatedCommit(cohort4);
+ verify(mockShard, never()).persistPayload(eq(cohort4.getIdentifier()), any(CommitTransactionPayload.class),
+ anyBoolean());
+ verifyNoMoreInteractions(commitCallback4);
+
+ final FutureCallback<UnsignedLong> commitCallback1 = coordinatedCommit(cohort1);
+ InOrder inOrder = inOrder(mockShard);
+ inOrder.verify(mockShard).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(true));
+ inOrder.verify(mockShard).persistPayload(eq(cohort2.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(false));
+ verifyNoMoreInteractions(commitCallback1);
+ verifyNoMoreInteractions(commitCallback2);
+
+ final FutureCallback<UnsignedLong> commitCallback3 = coordinatedCommit(cohort3);
+ inOrder = inOrder(mockShard);
+ inOrder.verify(mockShard).persistPayload(eq(cohort3.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(true));
+ inOrder.verify(mockShard).persistPayload(eq(cohort4.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(false));
+ verifyNoMoreInteractions(commitCallback3);
+ verifyNoMoreInteractions(commitCallback4);
+
+ final ShardDataTreeCohort cohort5 = newShardDataTreeCohort(snapshot ->
+ snapshot.merge(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
+ final FutureCallback<Void> canCommitCallback5 = coordinatedCanCommit(cohort5);
+
+ // The payload instance doesn't matter - it just needs to be of type CommitTransactionPayload.
+ CommitTransactionPayload mockPayload = CommitTransactionPayload.create(nextTransactionId(),
+ cohort1.getCandidate());
+ shardDataTree.applyReplicatedPayload(cohort1.getIdentifier(), mockPayload);
+ shardDataTree.applyReplicatedPayload(cohort2.getIdentifier(), mockPayload);
+ shardDataTree.applyReplicatedPayload(cohort3.getIdentifier(), mockPayload);
+ shardDataTree.applyReplicatedPayload(cohort4.getIdentifier(), mockPayload);
+
+ inOrder = inOrder(commitCallback1, commitCallback2, commitCallback3, commitCallback4);
+ inOrder.verify(commitCallback1).onSuccess(any(UnsignedLong.class));
+ inOrder.verify(commitCallback2).onSuccess(any(UnsignedLong.class));
+ inOrder.verify(commitCallback3).onSuccess(any(UnsignedLong.class));
+ inOrder.verify(commitCallback4).onSuccess(any(UnsignedLong.class));
+
+ verify(canCommitCallback5).onSuccess(null);
+
+ final DataTreeSnapshot snapshot =
+ shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
+ Optional<NormalizedNode> optional = snapshot.readNode(carPath);
+ assertTrue("Car node present", optional.isPresent());
+ assertEquals("Car node", carNode, optional.get());
+
+ optional = snapshot.readNode(PeopleModel.BASE_PATH);
+ assertTrue("People node present", optional.isPresent());
+ assertEquals("People node", peopleNode, optional.get());
+ }
+
+ @Test
+ public void testPipelinedTransactionsWithImmediateCommits() throws Exception {
+ final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
+
+ final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
+
+ YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
+ MapEntryNode carNode = CarsModel.newCarEntry("optima", Uint64.valueOf(100));
+ final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot -> snapshot.write(carPath, carNode));
+
+ final FutureCallback<UnsignedLong> commitCallback2 = immediate3PhaseCommit(cohort2);
+ final FutureCallback<UnsignedLong> commitCallback3 = immediate3PhaseCommit(cohort3);
+ final FutureCallback<UnsignedLong> commitCallback1 = immediate3PhaseCommit(cohort1);
+
+ InOrder inOrder = inOrder(mockShard);
+ inOrder.verify(mockShard).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(true));
+ inOrder.verify(mockShard).persistPayload(eq(cohort2.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(true));
+ inOrder.verify(mockShard).persistPayload(eq(cohort3.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(false));
+
+ // The payload instance doesn't matter - it just needs to be of type CommitTransactionPayload.
+ CommitTransactionPayload mockPayload = CommitTransactionPayload.create(nextTransactionId(),
+ cohort1.getCandidate());
+ shardDataTree.applyReplicatedPayload(cohort1.getIdentifier(), mockPayload);
+ shardDataTree.applyReplicatedPayload(cohort2.getIdentifier(), mockPayload);
+ shardDataTree.applyReplicatedPayload(cohort3.getIdentifier(), mockPayload);
+
+ inOrder = inOrder(commitCallback1, commitCallback2, commitCallback3);
+ inOrder.verify(commitCallback1).onSuccess(any(UnsignedLong.class));
+ inOrder.verify(commitCallback2).onSuccess(any(UnsignedLong.class));
+ inOrder.verify(commitCallback3).onSuccess(any(UnsignedLong.class));
+
+ final DataTreeSnapshot snapshot =
+ shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
+ Optional<NormalizedNode> optional = snapshot.readNode(carPath);
+ assertTrue("Car node present", optional.isPresent());
+ assertEquals("Car node", carNode, optional.get());
+ }
+
+ @Test
+ public void testPipelinedTransactionsWithImmediateReplication() {
+ immediatePayloadReplication(shardDataTree, mockShard);
+
+ final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
+
+ final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
+
+ YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
+ MapEntryNode carNode = CarsModel.newCarEntry("optima", Uint64.valueOf(100));
+ final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot -> snapshot.write(carPath, carNode));
+
+ final FutureCallback<UnsignedLong> commitCallback1 = immediate3PhaseCommit(cohort1);
+ final FutureCallback<UnsignedLong> commitCallback2 = immediate3PhaseCommit(cohort2);
+ final FutureCallback<UnsignedLong> commitCallback3 = immediate3PhaseCommit(cohort3);
+
+ InOrder inOrder = inOrder(commitCallback1, commitCallback2, commitCallback3);
+ inOrder.verify(commitCallback1).onSuccess(any(UnsignedLong.class));
+ inOrder.verify(commitCallback2).onSuccess(any(UnsignedLong.class));
+ inOrder.verify(commitCallback3).onSuccess(any(UnsignedLong.class));
+
+ final DataTreeSnapshot snapshot = shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
+ Optional<NormalizedNode> optional = snapshot.readNode(CarsModel.BASE_PATH);
+ assertTrue("Car node present", optional.isPresent());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testAbortWithPendingCommits() throws Exception {
+ final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
+
+ final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(PeopleModel.BASE_PATH, PeopleModel.create()));
+
+ final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
+
+ YangInstanceIdentifier carPath = CarsModel.newCarPath("optima");
+ MapEntryNode carNode = CarsModel.newCarEntry("optima", Uint64.valueOf(100));
+ final ShardDataTreeCohort cohort4 = newShardDataTreeCohort(snapshot -> snapshot.write(carPath, carNode));
+
+ coordinatedCanCommit(cohort2);
+ immediateCanCommit(cohort1);
+ coordinatedCanCommit(cohort3);
+ coordinatedCanCommit(cohort4);
+
+ coordinatedPreCommit(cohort1);
+ coordinatedPreCommit(cohort2);
+ coordinatedPreCommit(cohort3);
+
+ FutureCallback<Void> mockAbortCallback = mock(FutureCallback.class);
+ doNothing().when(mockAbortCallback).onSuccess(null);
+ cohort2.abort(mockAbortCallback);
+ verify(mockAbortCallback).onSuccess(null);
+
+ coordinatedPreCommit(cohort4);
+ coordinatedCommit(cohort1);
+ coordinatedCommit(cohort3);
+ coordinatedCommit(cohort4);
+
+ InOrder inOrder = inOrder(mockShard);
+ inOrder.verify(mockShard).persistPayload(eq(cohort1.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(false));
+ inOrder.verify(mockShard).persistPayload(eq(cohort3.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(false));
+ inOrder.verify(mockShard).persistPayload(eq(cohort4.getIdentifier()), any(CommitTransactionPayload.class),
+ eq(false));
+
+ // The payload instance doesn't matter - it just needs to be of type CommitTransactionPayload.
+ CommitTransactionPayload mockPayload = CommitTransactionPayload.create(nextTransactionId(),
+ cohort1.getCandidate());
+ shardDataTree.applyReplicatedPayload(cohort1.getIdentifier(), mockPayload);
+ shardDataTree.applyReplicatedPayload(cohort3.getIdentifier(), mockPayload);
+ shardDataTree.applyReplicatedPayload(cohort4.getIdentifier(), mockPayload);
+
+ final DataTreeSnapshot snapshot =
+ shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
+ Optional<NormalizedNode> optional = snapshot.readNode(carPath);
+ assertTrue("Car node present", optional.isPresent());
+ assertEquals("Car node", carNode, optional.get());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testAbortWithFailedRebase() {
+ immediatePayloadReplication(shardDataTree, mockShard);
+
+ final ShardDataTreeCohort cohort1 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.BASE_PATH, CarsModel.emptyContainer()));
+
+ final ShardDataTreeCohort cohort2 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode()));
+
+ NormalizedNode peopleNode = PeopleModel.create();
+ final ShardDataTreeCohort cohort3 = newShardDataTreeCohort(snapshot ->
+ snapshot.write(PeopleModel.BASE_PATH, peopleNode));
+
+ immediateCanCommit(cohort1);
+ FutureCallback<Void> canCommitCallback2 = coordinatedCanCommit(cohort2);
+
+ coordinatedPreCommit(cohort1);
+ verify(canCommitCallback2).onSuccess(null);
+
+ FutureCallback<Void> mockAbortCallback = mock(FutureCallback.class);
+ doNothing().when(mockAbortCallback).onSuccess(null);
+ cohort1.abort(mockAbortCallback);
+ verify(mockAbortCallback).onSuccess(null);
+
+ FutureCallback<DataTreeCandidate> preCommitCallback2 = coordinatedPreCommit(cohort2);
+ verify(preCommitCallback2).onFailure(any(Throwable.class));
+
+ immediateCanCommit(cohort3);
+ immediatePreCommit(cohort3);
+ immediateCommit(cohort3);
+
+ final DataTreeSnapshot snapshot =
+ shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
+ Optional<NormalizedNode> optional = snapshot.readNode(PeopleModel.BASE_PATH);
+ assertTrue("People node present", optional.isPresent());
+ assertEquals("People node", peopleNode, optional.get());
+ }
+
+ @Test
+ public void testUintCommitPayload() throws IOException {
+ shardDataTree.applyRecoveryPayload(CommitTransactionPayload.create(nextTransactionId(),
+ DataTreeCandidates.fromNormalizedNode(YangInstanceIdentifier.empty(), bigIntegerRoot()),
+ PayloadVersion.SODIUM_SR1));
+
+ assertCarsUint64();
+ }
+
+ @Test
+ public void testUintSnapshot() throws IOException, DataValidationFailedException {
+ shardDataTree.applyRecoverySnapshot(new ShardSnapshotState(new MetadataShardDataTreeSnapshot(bigIntegerRoot()),
+ true));
+
+ assertCarsUint64();
+ }
+
+ @Test
+ public void testUintReplay() throws DataValidationFailedException, IOException {
+ // Commit two writes and one merge, saving the data tree candidate for each.
+ // write(foo=1)
+ // write(foo=2)
+ // merge(foo=3)
+ final DataTree dataTree = new InMemoryDataTreeFactory().create(DataTreeConfiguration.DEFAULT_OPERATIONAL,
+ fullSchema);
+ DataTreeModification mod = dataTree.takeSnapshot().newModification();
+ mod.write(CarsModel.BASE_PATH, Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(CarsModel.BASE_QNAME))
+ .withChild(Builders.mapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(CarsModel.CAR_QNAME))
+ .withChild(createCar("one", BigInteger.ONE))
+ .build())
+ .build());
+ mod.ready();
+ dataTree.validate(mod);
+ final DataTreeCandidate first = dataTree.prepare(mod);
+ dataTree.commit(first);
+
+ mod = dataTree.takeSnapshot().newModification();
+ mod.write(CarsModel.newCarPath("two"), createCar("two", BigInteger.TWO));
+ mod.ready();
+ dataTree.validate(mod);
+ final DataTreeCandidate second = dataTree.prepare(mod);
+ dataTree.commit(second);
+
+ mod = dataTree.takeSnapshot().newModification();
+ mod.merge(CarsModel.CAR_LIST_PATH, Builders.mapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(CarsModel.CAR_QNAME))
+ .withChild(createCar("three", BigInteger.TEN))
+ .build());
+ mod.ready();
+ dataTree.validate(mod);
+ final DataTreeCandidate third = dataTree.prepare(mod);
+ dataTree.commit(third);
+
+ // Apply first candidate as a snapshot
+ shardDataTree.applyRecoverySnapshot(
+ new ShardSnapshotState(new MetadataShardDataTreeSnapshot(first.getRootNode().getDataAfter().get()), true));
+ // Apply the other two snapshots as transactions
+ shardDataTree.applyRecoveryPayload(CommitTransactionPayload.create(nextTransactionId(), second,
+ PayloadVersion.SODIUM_SR1));
+ shardDataTree.applyRecoveryPayload(CommitTransactionPayload.create(nextTransactionId(), third,
+ PayloadVersion.SODIUM_SR1));
+
+ // Verify uint translation
+ final DataTreeSnapshot snapshot = shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
+ final NormalizedNode cars = snapshot.readNode(CarsModel.CAR_LIST_PATH).get();
+
+ assertEquals(Builders.mapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(CarsModel.CAR_QNAME))
+ // Note: Uint64
+ .withChild(createCar("one", Uint64.ONE))
+ .withChild(createCar("two", Uint64.TWO))
+ .withChild(createCar("three", Uint64.TEN))
+ .build(), cars);
+ }
+
+ private void assertCarsUint64() {
+ final DataTreeSnapshot snapshot = shardDataTree.newReadOnlyTransaction(nextTransactionId()).getSnapshot();
+ final NormalizedNode cars = snapshot.readNode(CarsModel.CAR_LIST_PATH).get();