summaryrefslogtreecommitdiff
path: root/jstests/multiVersion/libs
diff options
context:
space:
mode:
authorVesselina Ratcheva <vesselina.ratcheva@10gen.com>2018-01-11 17:08:47 -0500
committerVesselina Ratcheva <vesselina.ratcheva@10gen.com>2018-01-30 13:31:30 -0500
commitbff8e6e0040d659dc2d70c28b98a42e9052a8743 (patch)
tree5dbdba07f4b6cc6392148591afeb144480d29a22 /jstests/multiVersion/libs
parente62e26b324bcf4584e53cf24f8e853842d294dba (diff)
downloadmongo-bff8e6e0040d659dc2d70c28b98a42e9052a8743.tar.gz
SERVER-31409 Create basic multi-version rollback Javascript test
Diffstat (limited to 'jstests/multiVersion/libs')
-rw-r--r--jstests/multiVersion/libs/multiversion_rollback.js168
1 files changed, 168 insertions, 0 deletions
diff --git a/jstests/multiVersion/libs/multiversion_rollback.js b/jstests/multiVersion/libs/multiversion_rollback.js
new file mode 100644
index 00000000000..ef90e497c60
--- /dev/null
+++ b/jstests/multiVersion/libs/multiversion_rollback.js
@@ -0,0 +1,168 @@
+/**
+ * The purpose of this test is to verify that simple CRUD operations are rolled back
+ * successfully in multiversion replica sets. This test induces communication between
+ * the rollback node and the sync source during rollback. This is done in order to
+ * exercise rollback via refetch in the case that refetch is necessary.
+ */
+
+'use strict';
+
+load("jstests/replsets/libs/rollback_test.js");
+load("jstests/libs/collection_drop_recreate.js");
+
+/**
+ * Executes and validates rollback between a pair of nodes with the given versions.
+ *
+ * @param {string} testName the name of the test being run
+ * @param {string} rollbackNodeVersion the desired version for the rollback node
+ * @param {string} syncSourceVersion the desired version for the sync source
+ *
+ */
+function testMultiversionRollback(testName, rollbackNodeVersion, syncSourceVersion) {
+ jsTestLog("Started multiversion rollback test for versions: {rollbackNode: " +
+ rollbackNodeVersion + ", syncSource: " + syncSourceVersion + "}.");
+
+ let dbName = testName;
+
+ let CommonOps = (node) => {
+ // Insert four documents on both nodes.
+ assert.writeOK(node.getDB(dbName)["bothNodesKeep"].insert({a: 1}));
+ assert.writeOK(node.getDB(dbName)["rollbackNodeDeletes"].insert({b: 1}));
+ assert.writeOK(node.getDB(dbName)["rollbackNodeUpdates"].insert({c: 1}));
+ assert.writeOK(node.getDB(dbName)["bothNodesUpdate"].insert({d: 1}));
+ };
+
+ let RollbackOps = (node) => {
+ // Perform operations only on the rollback node:
+ // 1. Delete a document.
+ // 2. Update a document only on this node.
+ // 3. Update a document on both nodes.
+ // All three documents will be refetched during rollback.
+ assert.writeOK(node.getDB(dbName)["rollbackNodeDeletes"].remove({b: 1}));
+ assert.writeOK(node.getDB(dbName)["rollbackNodeUpdates"].update({c: 1}, {c: 0}));
+ assert.writeOK(node.getDB(dbName)["bothNodesUpdate"].update({d: 1}, {d: 0}));
+ };
+
+ let SyncSourceOps = (node) => {
+ // Perform operations only on the sync source:
+ // 1. Make a conflicting write on one of the documents the rollback node updates.
+ // 2. Insert a new document.
+ assert.writeOK(node.getDB(dbName)["bothNodesUpdate"].update({d: 1}, {d: 2}));
+ assert.writeOK(node.getDB(dbName)["syncSourceInserts"].insert({e: 1}));
+ };
+
+ // Set up replica set.
+ let replSet = setupReplicaSet(testName, rollbackNodeVersion, syncSourceVersion);
+
+ // Set up Rollback Test.
+ let rollbackTest = new RollbackTest(testName, replSet);
+
+ CommonOps(rollbackTest.getPrimary());
+
+ // Perform operations that will be rolled back.
+ let rollbackNode = rollbackTest.transitionToRollbackOperations();
+ RollbackOps(rollbackNode);
+
+ // Perform different operations only on the sync source.
+ let syncSource = rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+ SyncSourceOps(syncSource);
+
+ // Wait for rollback to finish.
+ rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+ rollbackTest.transitionToSteadyStateOperations();
+
+ rollbackTest.stop();
+}
+
+/**
+ * Sets up a multiversion replica set.
+ *
+ * Note that, regardless of which node in the rollbackNode-syncSource pair requires
+ * which version, there is only one possible way to start up such a cluster:
+ *
+ * 1. Start up the first two nodes with the higher of the two versions.
+ * 2. Set the FCV to the lower version in order to be able to include the third node.
+ * 3. Bring up the third node and add it to the set, with the lower binary version.
+ * 4. This always results in a higher-version primary and a lower-version secondary,
+ * so if a test case specifies the lower version on the rollback node (i.e. the
+ * opposite setup), this function will force the current primary and secondary
+ * to switch roles.
+ *
+ * This function returns a replica set with the intended rollback node as the primary.
+ *
+ * @param {string} testName the name of the test being run
+ * @param {string} rollbackNodeVersion the desired version for the rollback node
+ * @param {string} syncSourceVersion the desired version for the sync source
+ */
+function setupReplicaSet(testName, rollbackNodeVersion, syncSourceVersion) {
+ jsTestLog(
+ `[${testName}] Beginning cluster setup with versions: {rollbackNode: ${rollbackNodeVersion},
+ syncSource: ${syncSourceVersion}}.`);
+
+ let sortedVersions =
+ [rollbackNodeVersion, syncSourceVersion].sort(MongoRunner.compareBinVersions);
+ let lowerVersion = MongoRunner.getBinVersionFor(sortedVersions[0]);
+ let higherVersion = MongoRunner.getBinVersionFor(sortedVersions[1]);
+
+ jsTestLog(`[${testName}] Starting up first two nodes with version: ${higherVersion}`);
+ var initialNodes = {n1: {binVersion: higherVersion}, a: {binVersion: higherVersion}};
+
+ // Start up a two-node cluster first.
+ var rst = new ReplSetTest({name: testName, nodes: initialNodes, useBridge: true});
+ rst.startSet();
+ rst.initiate();
+
+ // Wait for both nodes to be up.
+ waitForState(rst.nodes[0], ReplSetTest.State.PRIMARY);
+ waitForState(rst.nodes[1], ReplSetTest.State.ARBITER);
+
+ const initialPrimary = rst.getPrimary();
+
+ // Set FCV to accommodate third node.
+ jsTestLog(
+ `[${testName} - ${initialPrimary.host}] Setting FCV to ${lowerVersion} on the primary.`);
+ assert.commandWorked(
+ initialPrimary.adminCommand({setFeatureCompatibilityVersion: lowerVersion}));
+
+ jsTestLog(`[${testName}] Bringing up third node with version ${lowerVersion}`);
+ rst.add({binVersion: lowerVersion});
+ rst.reInitiate();
+
+ jsTestLog(
+ `[${testName} - ${rst.nodes[2].host}] Waiting for the newest node to become a secondary.`);
+ rst.awaitSecondaryNodes();
+
+ let primary = rst.nodes[0];
+ let secondary = rst.nodes[2];
+ let arbiter = rst.nodes[1];
+
+ // Make sure we still have the right node as the primary.
+ assert.eq(rst.getPrimary(), primary);
+
+ // Also make sure the other two nodes are in their expected states.
+ assert.eq(ReplSetTest.State.ARBITER, arbiter.adminCommand({replSetGetStatus: true}).myState);
+ assert.eq(ReplSetTest.State.SECONDARY,
+ secondary.adminCommand({replSetGetStatus: true}).myState);
+
+ jsTestLog(`[${testName}] Cluster now running with versions: {primary: ${higherVersion},
+ secondary: ${lowerVersion}, arbiter: ${higherVersion}}.`);
+
+ // Some test cases require that the primary (future rollback node) is running the lower
+ // version, which at this point is always on the secondary, so we elect that node instead.
+ if (rollbackNodeVersion === lowerVersion) {
+ jsTestLog(
+ `[${testName}] Test case requires opposite versions for primary and secondary. Swapping roles.`);
+
+ // Force the current secondary to become the primary.
+ rst.stepUp(secondary);
+
+ let newPrimary = secondary;
+ secondary = primary;
+ primary = newPrimary;
+
+ jsTestLog(`[${testName}] Cluster now running with versions: {primary: ${lowerVersion},
+ secondary: ${higherVersion}, arbiter: ${higherVersion}}.`);
+ }
+
+ return rst;
+}