mongo/jstests/replsets/rollback_drop_database.js

109 lines
5.0 KiB
JavaScript

/*
* Test that the server is able to roll back a 'dropDatabase' entry correctly. This test creates
* a collection, then executes a 'dropDatabase' command, partitioning the primary such that the
* final 'dropDatabase' oplog entry is not replicated. The test then forces rollback of that entry.
*
* The 'dropDatabase' command drops each collection, ensures that the last drop is majority
* committed, and only then logs a 'dropDatabase' oplog entry. This is therefore the only entry that
* could get rolled back.
*
* Additionally test handling of an incompletely dropped database across a replica set. If a primary
* writes a dropDatabase oplog entry and clears in-memory database state, but subsequently rolls
* back the dropDatabase oplog entry, then the replica set secondaries will still have the in-memory
* state. If the original primary is re-elected, it will allow a subsequent createCollection with a
* database name conflicting with the original database. The secondaries should close the original
* empty database and open the new database on receipt of the createCollection.
*
* @tags: [
* multiversion_incompatible,
* ]
*/
import {RollbackTest} from "jstests/replsets/libs/rollback_test.js";
const testName = "rollback_drop_database";
// MongoDB does not allow multiple databases to exist that differ only in letter case. These
// database names will differ only in letter case, to test that secondaries will safely close
// conflicting empty databases.
const dbName = "olddatabase";
const conflictingDbName = "OLDDATABASE";
let rollbackTest = new RollbackTest(testName);
let rollbackNode = rollbackTest.getPrimary();
let syncSourceNode = rollbackTest.getSecondary();
// Perform initial insert (common operation).
assert.commandWorked(rollbackNode.getDB(dbName)["beforeRollback"].insert({"num": 1}));
// Set a failpoint on the original primary, so that it blocks after it commits the last
// 'dropCollection' entry but before the 'dropDatabase' entry is logged.
assert.commandWorked(
rollbackNode.adminCommand({configureFailPoint: "dropDatabaseHangBeforeInMemoryDrop", mode: "alwaysOn"}),
);
// Issue a 'dropDatabase' command.
let dropDatabaseFn = function () {
const rollbackDb = "olddatabase";
let primary = db.getMongo();
jsTestLog("Dropping database " + rollbackDb + " on primary node " + primary.host);
let dbToDrop = db.getSiblingDB(rollbackDb);
assert.commandWorked(dbToDrop.dropDatabase({w: 1}));
};
let waitForDropDatabaseToFinish = startParallelShell(dropDatabaseFn, rollbackNode.port);
// Ensure that we've hit the failpoint before moving on.
checkLog.contains(rollbackNode, "dropDatabase - fail point dropDatabaseHangBeforeInMemoryDrop enabled");
// Wait for the secondary to finish dropping the collection (the last replicated entry).
// We use the default 10-minute timeout for this.
assert.soon(
function () {
let res = syncSourceNode.getDB(dbName).getCollectionNames().includes("beforeRollback");
return !res;
},
"Sync source did not finish dropping collection beforeRollback",
10 * 60 * 1000,
);
rollbackTest.transitionToRollbackOperations();
// Check that the dropDatabase oplog entry has not been written.
assert(!checkLog.checkContainsOnceJson(rollbackNode, 7360105));
// Allow the final 'dropDatabase' entry to be logged on the now isolated primary.
// This is the rollback node's divergent oplog entry.
assert.commandWorked(
rollbackNode.adminCommand({configureFailPoint: "dropDatabaseHangBeforeInMemoryDrop", mode: "off"}),
);
waitForDropDatabaseToFinish();
// Check that the dropDatabase oplog entry has now been written.
assert(checkLog.checkContainsOnceJson(rollbackNode, 7360105));
assert.eq(false, rollbackNode.getDB(dbName).getCollectionNames().includes("beforeRollback"));
jsTestLog("Database " + dbName + " successfully dropped on primary node " + rollbackNode.host);
rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
// Perform an insert on another database while interfacing with the new primary.
// This is the sync source's divergent oplog entry.
assert.commandWorked(syncSourceNode.getDB("someDB")["afterRollback"].insert({"num": 2}));
rollbackTest.transitionToSyncSourceOperationsDuringRollback();
rollbackTest.transitionToSteadyStateOperations();
jsTestLog("Transitioned to steady state, going to run test operations");
// Check that replication rollback occurred on the old primary.
assert(checkLog.checkContainsOnceJson(rollbackNode, 21612));
// The syncSourceNode never received the dropDatabase oplog entry from the rollbackNode. Therefore,
// syncSourceNode never cleared the in-memory database state for that database. Check that
// syncSourceNode will safely clear the original empty database when applying a createCollection
// with a new database name that conflicts with the original.
rollbackTest.stepUpNode(rollbackNode);
// Using only w:2 because the third node is frozen / not replicating.
assert.commandWorked(rollbackNode.getDB(conflictingDbName)["afterRollback"].insert({"num": 2}, {writeConcern: {w: 2}}));
rollbackTest.stop();