Fix StandaloneFrontendHistory accounting 96/105196/1
authorŠimon Ukuš <simon.ukus@pantheon.tech>
Fri, 17 Mar 2023 08:58:40 +0000 (09:58 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 3 Apr 2023 18:47:45 +0000 (20:47 +0200)
testTransactionForwardedToLeaderAfterRetry() is exposing a scheduling
difference between the two implementations -- with tell-based protocol
we are holding off sending modifications until there is a significant
event needing them to be flushed to the backend.

Fixing this up exposes a bug in StandaloneFrontendHistory transaction
accounting, as circles back to ShardDataTree, where its transactions
are accounted for again -- fix that mistake by allocating transactions
directly in StandaloneFrontendHistory.

JIRA: CONTROLLER-2017
Change-Id: Ied00910f55bd3c63f99ae187396c1a193e5ece20
Signed-off-by: Šimon Ukuš <simon.ukus@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit f70fb5eaee514ffa5963049ac12286c2dee4ff80)

opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/StandaloneFrontendHistory.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreRemotingIntegrationTest.java

index d26b23e9dc228edce2f6c755bec308116d7f6039..df13cd1368243ac8c128bc2eccf9c5ba14617bca 100644 (file)
@@ -60,12 +60,14 @@ final class StandaloneFrontendHistory extends AbstractFrontendHistory {
 
     @Override
     FrontendTransaction createOpenSnapshot(final TransactionIdentifier id) {
-        return FrontendReadOnlyTransaction.create(this, tree.newReadOnlyTransaction(id));
+        return FrontendReadOnlyTransaction.create(this,
+            new ReadOnlyShardDataTreeTransaction(tree, id, tree.takeSnapshot()));
     }
 
     @Override
     FrontendTransaction createOpenTransaction(final TransactionIdentifier id) {
-        return FrontendReadWriteTransaction.createOpen(this, tree.newReadWriteTransaction(id));
+        return FrontendReadWriteTransaction.createOpen(this,
+            new ReadWriteShardDataTreeTransaction(tree, id, tree.takeSnapshot().newModification()));
     }
 
     @Override
index 4795cb2ae46b89a7328ccf0e755f56e30ad2e262..e5f3c958354114fa0e2dade15b728b60882be941 100644 (file)
@@ -897,6 +897,12 @@ public class DistributedDataStoreRemotingIntegrationTest extends AbstractTest {
         leaderDatastoreContextBuilder.shardBatchedModificationCount(2);
         initDatastoresWithCarsAndPeople("testTransactionForwardedToLeaderAfterRetry");
 
+        // Verify backend statistics on start
+        IntegrationTestKit.verifyShardStats(leaderDistributedDataStore, "cars",
+            stats -> assertEquals("getReadWriteTransactionCount", 0, stats.getReadWriteTransactionCount()));
+        IntegrationTestKit.verifyShardStats(followerDistributedDataStore, "cars",
+            stats -> assertEquals("getReadWriteTransactionCount", 0, stats.getReadWriteTransactionCount()));
+
         // Do an initial write to get the primary shard info cached.
 
         final DOMStoreWriteTransaction initialWriteTx = followerDistributedDataStore.newWriteOnlyTransaction();
@@ -936,6 +942,12 @@ public class DistributedDataStoreRemotingIntegrationTest extends AbstractTest {
         writeTx2.write(PeopleModel.PERSON_LIST_PATH, people);
         final DOMStoreThreePhaseCommitCohort writeTx2Cohort = writeTx2.ready();
 
+        // At this point only leader should see the transactions
+        IntegrationTestKit.verifyShardStats(leaderDistributedDataStore, "cars",
+            stats -> assertEquals("getReadWriteTransactionCount", 2, stats.getReadWriteTransactionCount()));
+        IntegrationTestKit.verifyShardStats(followerDistributedDataStore, "cars",
+            stats -> assertEquals("getReadWriteTransactionCount", 0, stats.getReadWriteTransactionCount()));
+
         // Prepare another WO that writes to a single shard and thus will be directly committed on ready. This
         // tx writes 5 cars so 2 BatchedModifications messages will be sent initially and cached in the leader shard
         // (with shardBatchedModificationCount set to 2). The 3rd BatchedModifications will be sent on ready.
@@ -959,12 +971,16 @@ public class DistributedDataStoreRemotingIntegrationTest extends AbstractTest {
 
         final DOMStoreReadWriteTransaction readWriteTx = followerDistributedDataStore.newReadWriteTransaction();
         cars.add(CarsModel.newCarEntry("car" + carIndex, Uint64.valueOf(carIndex)));
-        readWriteTx.write(CarsModel.newCarPath("car" + carIndex), cars.getLast());
-
-        // FIXME: CONTROLLER-2017: ClientBackedDataStore reports only 4 transactions
-        assumeTrue(DistributedDataStore.class.isAssignableFrom(testParameter));
+        final YangInstanceIdentifier carPath = CarsModel.newCarPath("car" + carIndex);
+        readWriteTx.write(carPath, cars.getLast());
+
+        // There is a difference here between implementations: tell-based protocol will postpone write operations until
+        // either a read is made or the transaction is submitted. Here we flush out the last transaction, so we see
+        // three transactions, not just the ones we have started committing
+        assertTrue(readWriteTx.exists(carPath).get(2, TimeUnit.SECONDS));
+        final int earlyTxCount = DistributedDataStore.class.isAssignableFrom(testParameter) ? 5 : 3;
         IntegrationTestKit.verifyShardStats(leaderDistributedDataStore, "cars",
-            stats -> assertEquals("getReadWriteTransactionCount", 5, stats.getReadWriteTransactionCount()));
+            stats -> assertEquals("getReadWriteTransactionCount", earlyTxCount, stats.getReadWriteTransactionCount()));
 
         // Disable elections on the leader so it switches to follower.
 
@@ -997,6 +1013,13 @@ public class DistributedDataStoreRemotingIntegrationTest extends AbstractTest {
         followerTestKit.doCommit(writeTx4Cohort);
         followerTestKit.doCommit(rwTxCohort);
 
+        // At this point everything is committed and the follower datastore should see 5 transactions, but leader should
+        // only see the initial transactions
+        IntegrationTestKit.verifyShardStats(leaderDistributedDataStore, "cars",
+            stats -> assertEquals("getReadWriteTransactionCount", earlyTxCount, stats.getReadWriteTransactionCount()));
+        IntegrationTestKit.verifyShardStats(followerDistributedDataStore, "cars",
+            stats -> assertEquals("getReadWriteTransactionCount", 5, stats.getReadWriteTransactionCount()));
+
         DOMStoreReadTransaction readTx = leaderDistributedDataStore.newReadOnlyTransaction();
         verifyCars(readTx, cars.toArray(new MapEntryNode[cars.size()]));
         verifyNode(readTx, PeopleModel.PERSON_LIST_PATH, people);