diff options
author | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2018-01-11 17:08:47 -0500 |
---|---|---|
committer | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2018-01-30 13:31:30 -0500 |
commit | bff8e6e0040d659dc2d70c28b98a42e9052a8743 (patch) | |
tree | 5dbdba07f4b6cc6392148591afeb144480d29a22 /jstests/multiVersion/libs | |
parent | e62e26b324bcf4584e53cf24f8e853842d294dba (diff) | |
download | mongo-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.js | 168 |
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; +} |