+ @SuppressWarnings("IllegalCatch")
+ @Test
+ public void testRaftCallbackDuringLeadershipDrop() throws Exception {
+ final String testName = "testRaftCallbackDuringLeadershipDrop";
+ initDatastores(testName, MODULE_SHARDS_CARS_1_2_3, CARS);
+
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ final IntegrationTestKit follower2TestKit = new IntegrationTestKit(follower2System,
+ DatastoreContext.newBuilderFrom(followerDatastoreContextBuilder.build()).operationTimeoutInMillis(500)
+ .shardLeaderElectionTimeoutInSeconds(3600),
+ commitTimeout);
+
+ final DOMStoreWriteTransaction initialWriteTx = leaderDistributedDataStore.newWriteOnlyTransaction();
+ initialWriteTx.write(CarsModel.BASE_PATH, CarsModel.emptyContainer());
+ leaderTestKit.doCommit(initialWriteTx.ready());
+
+ try (AbstractDataStore follower2DistributedDataStore = follower2TestKit.setupAbstractDataStore(
+ testParameter, testName, MODULE_SHARDS_CARS_1_2_3, false)) {
+
+ final ActorRef member3Cars = ((LocalShardStore) follower2DistributedDataStore).getLocalShards()
+ .getLocalShards().get("cars").getActor();
+ final ActorRef member2Cars = ((LocalShardStore)followerDistributedDataStore).getLocalShards()
+ .getLocalShards().get("cars").getActor();
+ member2Cars.tell(new StartDropMessages(AppendEntries.class), null);
+ member3Cars.tell(new StartDropMessages(AppendEntries.class), null);
+
+ final DOMStoreWriteTransaction newTx = leaderDistributedDataStore.newWriteOnlyTransaction();
+ newTx.write(CarsModel.CAR_LIST_PATH, CarsModel.newCarMapNode());
+ final AtomicBoolean submitDone = new AtomicBoolean(false);
+ executor.submit(() -> {
+ try {
+ leaderTestKit.doCommit(newTx.ready());
+ submitDone.set(true);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ final ActorRef leaderCars = ((LocalShardStore) leaderDistributedDataStore).getLocalShards()
+ .getLocalShards().get("cars").getActor();
+ await().atMost(10, TimeUnit.SECONDS)
+ .until(() -> ((OnDemandRaftState) leaderDistributedDataStore.getActorUtils()
+ .executeOperation(leaderCars, GetOnDemandRaftState.INSTANCE)).getLastIndex() >= 1);
+
+ final OnDemandRaftState raftState = (OnDemandRaftState)leaderDistributedDataStore.getActorUtils()
+ .executeOperation(leaderCars, GetOnDemandRaftState.INSTANCE);
+
+ // Simulate a follower not receiving heartbeats but still being able to send messages ie RequestVote with
+ // new term(switching to candidate after election timeout)
+ leaderCars.tell(new RequestVote(raftState.getCurrentTerm() + 1,
+ "member-3-shard-cars-testRaftCallbackDuringLeadershipDrop", -1,
+ -1), member3Cars);
+
+ member2Cars.tell(new StopDropMessages(AppendEntries.class), null);
+ member3Cars.tell(new StopDropMessages(AppendEntries.class), null);
+
+ await("Is tx stuck in COMMIT_PENDING")
+ .atMost(10, TimeUnit.SECONDS).untilAtomic(submitDone, equalTo(true));
+
+ }
+
+ executor.shutdownNow();
+ }
+
+ @Test
+ public void testSnapshotOnRootOverwrite() throws Exception {
+ // FIXME: ClientBackedDatastore does not have stable indexes/term, the snapshot index seems to fluctuate
+ assumeTrue(DistributedDataStore.class.isAssignableFrom(testParameter));
+
+ final String testName = "testSnapshotOnRootOverwrite";
+ final String[] shards = {"cars", "default"};
+ initDatastores(testName, "module-shards-default-cars-member1-and-2.conf", shards,
+ leaderDatastoreContextBuilder.snapshotOnRootOverwrite(true),
+ followerDatastoreContextBuilder.snapshotOnRootOverwrite(true));
+
+ leaderTestKit.waitForMembersUp("member-2");
+ final ContainerNode rootNode = ImmutableContainerNodeBuilder.create()
+ .withNodeIdentifier(YangInstanceIdentifier.NodeIdentifier.create(SchemaContext.NAME))
+ .withChild((ContainerNode) CarsModel.create())
+ .build();
+
+ leaderTestKit.testWriteTransaction(leaderDistributedDataStore, YangInstanceIdentifier.empty(), rootNode);
+
+ IntegrationTestKit.verifyShardState(leaderDistributedDataStore, "cars",
+ state -> assertEquals(1, state.getSnapshotIndex()));
+
+ IntegrationTestKit.verifyShardState(followerDistributedDataStore, "cars",
+ state -> assertEquals(1, state.getSnapshotIndex()));
+
+ verifySnapshot("member-1-shard-cars-testSnapshotOnRootOverwrite", 1);
+ verifySnapshot("member-2-shard-cars-testSnapshotOnRootOverwrite", 1);
+
+ for (int i = 0; i < 10; i++) {
+ leaderTestKit.testWriteTransaction(leaderDistributedDataStore, CarsModel.newCarPath("car " + i),
+ CarsModel.newCarEntry("car " + i, Uint64.ONE));
+ }
+
+ // fake snapshot causes the snapshotIndex to move
+ IntegrationTestKit.verifyShardState(leaderDistributedDataStore, "cars",
+ state -> assertEquals(10, state.getSnapshotIndex()));
+ IntegrationTestKit.verifyShardState(followerDistributedDataStore, "cars",
+ state -> assertEquals(10, state.getSnapshotIndex()));
+
+ // however the real snapshot still has not changed and was taken at index 1
+ verifySnapshot("member-1-shard-cars-testSnapshotOnRootOverwrite", 1);
+ verifySnapshot("member-2-shard-cars-testSnapshotOnRootOverwrite", 1);
+
+ // root overwrite so expect a snapshot
+ leaderTestKit.testWriteTransaction(leaderDistributedDataStore, YangInstanceIdentifier.empty(), rootNode);
+
+ // this was a real snapshot so everything should be in it(1(DisableTrackingPayload) + 1 + 10 + 1)
+ IntegrationTestKit.verifyShardState(leaderDistributedDataStore, "cars",
+ state -> assertEquals(12, state.getSnapshotIndex()));
+ IntegrationTestKit.verifyShardState(followerDistributedDataStore, "cars",
+ state -> assertEquals(12, state.getSnapshotIndex()));
+
+ verifySnapshot("member-1-shard-cars-testSnapshotOnRootOverwrite", 12);
+ verifySnapshot("member-2-shard-cars-testSnapshotOnRootOverwrite", 12);
+ }
+
+ private void verifySnapshot(final String persistenceId, final long lastAppliedIndex) {
+ await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
+ List<Snapshot> snap = InMemorySnapshotStore.getSnapshots(persistenceId, Snapshot.class);
+ assertEquals(1, snap.size());
+ assertEquals(lastAppliedIndex, snap.get(0).getLastAppliedIndex());
+ }
+ );
+ }
+