summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavithra Vetriselvan <pavithra.vetriselvan@mongodb.com>2019-09-24 21:10:50 +0000
committerevergreen <evergreen@mongodb.com>2019-09-24 21:10:50 +0000
commitd6c8abc4c077e2de13311a92781d9908bb45c397 (patch)
treea56241b9cb649989794eae9bad94111486d01e5c
parent1781933c126d2aff697cdb42a8f84b8212db23ad (diff)
downloadmongo-d6c8abc4c077e2de13311a92781d9908bb45c397.tar.gz
SERVER-41955 test that secondary can replicate prepare oplog entry received during initial sync
(cherry picked from commit f3b5de8769af3916cd199cdb324323a930f3581f)
-rw-r--r--jstests/replsets/initial_sync_replicates_prepare_received_during_another_initial_sync.js115
1 files changed, 115 insertions, 0 deletions
diff --git a/jstests/replsets/initial_sync_replicates_prepare_received_during_another_initial_sync.js b/jstests/replsets/initial_sync_replicates_prepare_received_during_another_initial_sync.js
new file mode 100644
index 00000000000..4c147802fbd
--- /dev/null
+++ b/jstests/replsets/initial_sync_replicates_prepare_received_during_another_initial_sync.js
@@ -0,0 +1,115 @@
+/**
+ * This test involves three nodes and runs as follows:
+ * - Node A is primary and accepts a prepared transaction.
+ * - Node B is restarted and starts initial syncing from Node A.
+ * - Node B finishes initial sync and the transaction is still prepared.
+ * - Node C is restarted and starts initial syncing from Node B.
+ * - Node C finishes initial sync and the transaction is still prepared.
+ * - Node A commits the transaction.
+ *
+ * This ensures that we can correctly replicate prepare oplog entries that were received
+ * during initial sync.
+ *
+ * @tags: [uses_transactions, uses_prepare_transaction]
+ */
+
+(function() {
+"use strict";
+load("jstests/core/txns/libs/prepare_helpers.js");
+load("jstests/libs/check_log.js");
+load("jstests/replsets/rslib.js");
+
+/**
+ * Restarts a secondary node so that it goes through initial sync and forces it to sync from
+ * a specific sync source.
+ *
+ * We will confirm that the secondary properly replicated the prepare oplog entry by performing
+ * an afterClusterTime read that encounters a prepare conflict.
+ *
+ */
+function restartSecondaryAndForceSyncSource(replSet, secondary, syncSource, dbName, clusterTime) {
+ // Restart secondary with startClean: true so that it goes through initial sync.
+ replSet.stop(secondary,
+ // signal
+ undefined,
+ // Validation would encounter a prepare conflict on the open transaction.
+ {skipValidation: true});
+ replSet.start(secondary,
+ {
+ startClean: true,
+ // Force this secondary to sync from the primary.
+ setParameter: {
+ 'failpoint.forceSyncSourceCandidate':
+ tojson({mode: 'alwaysOn', data: {hostAndPort: syncSource.host}}),
+ }
+ },
+ true /* wait */);
+
+ // Wait for the secondary to complete initial sync.
+ waitForState(secondary, ReplSetTest.State.SECONDARY);
+ // Allow for secondary reads.
+ secondary.setSlaveOk();
+ const secondaryDB = secondary.getDB(dbName);
+
+ // Confirm that we have a prepared transaction in progress on the secondary.
+ // Use a 5 second timeout so that there is enough time for the prepared transaction to
+ // release its locks and for the command to obtain those locks.
+ assert.commandFailedWithCode(
+ // Use afterClusterTime read to make sure that it will block on a prepare conflict.
+ secondaryDB.runCommand({
+ find: name,
+ filter: {_id: 0},
+ readConcern: {afterClusterTime: clusterTime},
+ maxTimeMS: 5000
+ }),
+ ErrorCodes.MaxTimeMSExpired);
+}
+
+// Add secondary nodes with priority: 0 and votes: 0 so that we prevent elections while
+// syncing from the primary.
+const name = jsTestName();
+const replSet = new ReplSetTest({
+ name: name,
+ nodes: [{}, {rsConfig: {priority: 0, votes: 0}}, {rsConfig: {priority: 0, votes: 0}}],
+});
+
+replSet.startSet();
+replSet.initiate();
+const primary = replSet.getPrimary();
+const secondaries = replSet.getSecondaries();
+const secondary1 = secondaries[0];
+const secondary2 = secondaries[1];
+const dbName = 'test';
+
+const coll = primary.getDB(dbName).getCollection(name);
+// Insert document that will be updated by a prepared transaction.
+assert.commandWorked(coll.insert({_id: 0, x: 1}));
+
+const session = primary.startSession();
+const sessionDB = session.getDatabase(dbName);
+const sessionColl = sessionDB.getCollection(name);
+
+jsTestLog("Preparing a transaction on the primary.");
+session.startTransaction();
+assert.commandWorked(sessionColl.update({_id: 0}, {x: 2}));
+const prepareTimestamp = PrepareHelpers.prepareTransaction(session);
+
+// Advance the clusterTime with another insert.
+const clusterTimeAfterPrepare =
+ assert.commandWorked(coll.runCommand("insert", {documents: [{advanceClusterTime: 1}]}))
+ .operationTime;
+
+jsTestLog("Restarting secondary1");
+restartSecondaryAndForceSyncSource(replSet, secondary1, primary, dbName, clusterTimeAfterPrepare);
+jsTestLog("secondary1 successfully replicated prepared transaction after initial sync");
+
+jsTestLog("Restarting secondary2");
+restartSecondaryAndForceSyncSource(
+ replSet, secondary2, secondary1, dbName, clusterTimeAfterPrepare);
+jsTestLog("secondary2 successfully replicated prepared transaction after initial sync");
+
+// Commit the transaction.
+assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));
+
+replSet.stopSet();
+})(); \ No newline at end of file