mirror of https://github.com/mongodb/mongo
226 lines
9.4 KiB
JavaScript
226 lines
9.4 KiB
JavaScript
/**
|
|
* Test that CollectionCloner completes without error when a collection is renamed during cloning.
|
|
*/
|
|
|
|
(function() {
|
|
"use strict";
|
|
|
|
load("jstests/libs/fail_point_util.js");
|
|
load("jstests/libs/uuid_util.js");
|
|
load('jstests/replsets/libs/two_phase_drops.js');
|
|
load("jstests/libs/logv2_helpers.js");
|
|
|
|
// Set up replica set. Disallow chaining so nodes always sync from primary.
|
|
const testName = "initial_sync_rename_collection";
|
|
const dbName = testName;
|
|
const replTest = new ReplSetTest(
|
|
{name: testName, nodes: [{}, {rsConfig: {priority: 0}}], settings: {chainingAllowed: false}});
|
|
replTest.startSet();
|
|
replTest.initiate();
|
|
|
|
const collName = "testcoll";
|
|
const primary = replTest.getPrimary();
|
|
const primaryDB = primary.getDB(dbName);
|
|
const primaryColl = primaryDB[collName];
|
|
const pRenameColl = primaryDB["r_" + collName];
|
|
|
|
// The default WC is majority and this test can't satisfy majority writes.
|
|
assert.commandWorked(primary.adminCommand(
|
|
{setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}, writeConcern: {w: "majority"}}));
|
|
|
|
// Used for cross-DB renames.
|
|
const secondDbName = testName + "_cross";
|
|
const primarySecondDB = primary.getDB(secondDbName);
|
|
const pCrossDBRenameColl = primarySecondDB[collName + "_cross"];
|
|
|
|
const nss = primaryColl.getFullName();
|
|
const rnss = pRenameColl.getFullName();
|
|
let secondary = replTest.getSecondary();
|
|
let secondaryDB = secondary.getDB(dbName);
|
|
let secondaryColl = secondaryDB[collName];
|
|
|
|
// This function adds data to the collection, restarts the secondary node with the given
|
|
// parameters and setting the given failpoint, waits for the failpoint to be hit,
|
|
// renames the collection, then disables the failpoint. It then optionally waits for the
|
|
// expectedLog message and waits for the secondary to complete initial sync, then ensures
|
|
// the collection on the secondary has been properly cloned.
|
|
function setupTest({failPoint, extraFailPointData, secondaryStartupParams}) {
|
|
jsTestLog("Writing data to collection.");
|
|
assert.commandWorked(primaryColl.insert([{_id: 1}, {_id: 2}]));
|
|
const data = Object.merge(extraFailPointData || {}, {nss: nss});
|
|
|
|
jsTestLog("Restarting secondary with failPoint " + failPoint + " set for " + nss);
|
|
secondaryStartupParams = secondaryStartupParams || {};
|
|
secondaryStartupParams['failpoint.' + failPoint] = tojson({mode: 'alwaysOn', data: data});
|
|
// Skip clearing initial sync progress after a successful initial sync attempt so that we
|
|
// can check initialSyncStatus fields after initial sync is complete.
|
|
secondaryStartupParams['failpoint.skipClearInitialSyncState'] = tojson({mode: 'alwaysOn'});
|
|
secondaryStartupParams['numInitialSyncAttempts'] = 1;
|
|
secondary =
|
|
replTest.restart(secondary, {startClean: true, setParameter: secondaryStartupParams});
|
|
secondaryDB = secondary.getDB(dbName);
|
|
secondaryColl = secondaryDB[collName];
|
|
|
|
jsTestLog("Waiting for secondary to reach failPoint " + failPoint);
|
|
assert.commandWorked(secondary.adminCommand({
|
|
waitForFailPoint: failPoint,
|
|
timesEntered: 1,
|
|
maxTimeMS: kDefaultWaitForFailPointTimeout
|
|
}));
|
|
|
|
// Restarting the secondary may have resulted in an election. Wait until the system
|
|
// stabilizes and reaches RS_STARTUP2 state.
|
|
replTest.getPrimary();
|
|
replTest.waitForState(secondary, ReplSetTest.State.STARTUP_2);
|
|
}
|
|
|
|
function finishTest({failPoint, expectedLog, createNew, renameAcrossDBs}) {
|
|
// Get the uuid for use in checking the log line.
|
|
const uuid_obj = getUUIDFromListCollections(primaryDB, collName);
|
|
const uuid = extractUUIDFromObject(uuid_obj);
|
|
const target = (renameAcrossDBs ? pCrossDBRenameColl : pRenameColl);
|
|
|
|
jsTestLog("Renaming collection on primary: " + target.getFullName());
|
|
assert.commandWorked(primary.adminCommand({
|
|
renameCollection: primaryColl.getFullName(),
|
|
to: target.getFullName(),
|
|
dropTarget: false
|
|
}));
|
|
|
|
// Only set for test cases that use 'system.drop' namespaces when dropping collections.
|
|
// In those tests the variable 'dropPendingNss' represents such a namespace. Used for
|
|
// expectedLog. See test cases 6 and 8 below.
|
|
let dropPendingNss;
|
|
const dropPendingColl =
|
|
TwoPhaseDropCollectionTest.collectionIsPendingDropInDatabase(primaryDB, collName);
|
|
if (dropPendingColl) {
|
|
dropPendingNss = dbName + "." + dropPendingColl["name"];
|
|
}
|
|
|
|
if (createNew) {
|
|
jsTestLog("Creating a new collection with the same name: " + primaryColl.getFullName());
|
|
assert.commandWorked(primaryColl.insert({_id: "not the same collection"}));
|
|
}
|
|
|
|
jsTestLog("Allowing secondary to continue.");
|
|
assert.commandWorked(secondary.adminCommand({configureFailPoint: failPoint, mode: 'off'}));
|
|
|
|
if (expectedLog) {
|
|
expectedLog = eval(expectedLog);
|
|
jsTestLog(expectedLog);
|
|
checkLog.contains(secondary, expectedLog);
|
|
}
|
|
|
|
jsTestLog("Waiting for initial sync to complete.");
|
|
replTest.waitForState(secondary, ReplSetTest.State.SECONDARY);
|
|
|
|
let res = assert.commandWorked(secondary.adminCommand({replSetGetStatus: 1}));
|
|
assert.eq(0, res.initialSyncStatus.failedInitialSyncAttempts);
|
|
|
|
if (createNew) {
|
|
assert.eq([{_id: "not the same collection"}], secondaryColl.find().toArray());
|
|
assert(primaryColl.drop());
|
|
} else {
|
|
assert.eq(0, secondaryColl.find().itcount());
|
|
}
|
|
replTest.checkReplicatedDataHashes();
|
|
|
|
// Drop the renamed collection so we can start fresh the next time around.
|
|
assert(target.drop());
|
|
}
|
|
|
|
function runRenameTest(params) {
|
|
setupTest(params);
|
|
finishTest(params);
|
|
}
|
|
|
|
jsTestLog("[1] Testing rename between listIndexes and find.");
|
|
runRenameTest({
|
|
failPoint: "hangBeforeClonerStage",
|
|
extraFailPointData: {cloner: "CollectionCloner", stage: "query"}
|
|
});
|
|
|
|
jsTestLog("[2] Testing cross-DB rename between listIndexes and find.");
|
|
runRenameTest({
|
|
failPoint: "hangBeforeClonerStage",
|
|
extraFailPointData: {cloner: "CollectionCloner", stage: "query"},
|
|
renameAcrossDBs: true
|
|
});
|
|
|
|
jsTestLog(
|
|
"[3] Testing rename between listIndexes and find, with new same-name collection created.");
|
|
runRenameTest({
|
|
failPoint: "hangBeforeClonerStage",
|
|
extraFailPointData: {cloner: "CollectionCloner", stage: "query"},
|
|
createNew: true
|
|
});
|
|
|
|
jsTestLog(
|
|
"[4] Testing cross-DB rename between listIndexes and find, with new same-name collection created.");
|
|
runRenameTest({
|
|
failPoint: "hangBeforeClonerStage",
|
|
extraFailPointData: {cloner: "CollectionCloner", stage: "query"},
|
|
createNew: true,
|
|
renameAcrossDBs: true
|
|
});
|
|
|
|
const expectedLogFor5and7 = isJsonLogNoConn()
|
|
? '`Sync process retrying cloner stage due to error","attr":{"cloner":"CollectionCloner","stage":"query","error":{"code":175,"codeName":"QueryPlanKilled","errmsg":"collection renamed from \'${nss}\' to \'${rnss}\'. UUID ${uuid}"}}}`'
|
|
: "`Sync process retrying CollectionCloner stage query due to QueryPlanKilled: collection renamed from '${nss}' to '${rnss}'. UUID ${uuid}`";
|
|
|
|
jsTestLog("[5] Testing rename between getMores.");
|
|
runRenameTest({
|
|
failPoint: "initialSyncHangCollectionClonerAfterHandlingBatchResponse",
|
|
secondaryStartupParams: {collectionClonerBatchSize: 1},
|
|
expectedLog: expectedLogFor5and7
|
|
});
|
|
|
|
// A cross-DB rename will appear as a drop in the context of the source DB.
|
|
let expectedLogFor6and8 =
|
|
"`CollectionCloner ns: '${nss}' uuid: UUID(\"${uuid}\") stopped because collection was dropped on source.`";
|
|
|
|
if (isJsonLogNoConn()) {
|
|
// Double escape the backslash as eval will do unescaping
|
|
expectedLogFor6and8 =
|
|
'`CollectionCloner stopped because collection was dropped on source","attr":{"namespace":"${nss}","uuid":{"uuid":{"$uuid":"${uuid}"}}}}`';
|
|
}
|
|
|
|
// We don't support 4.2 style two-phase drops with EMRC=false - in that configuration, the
|
|
// collection will instead be renamed to a <db>.system.drop.* namespace before being dropped. Since
|
|
// the cloner queries collection by UUID, it will observe the first drop phase as a rename.
|
|
// We still want to check that initial sync succeeds in such a case.
|
|
if (TwoPhaseDropCollectionTest.supportsDropPendingNamespaces(replTest)) {
|
|
if (isJsonLogNoConn()) {
|
|
expectedLogFor6and8 =
|
|
'`Sync process retrying cloner stage due to error","attr":{"cloner":"CollectionCloner","stage":"query","error":{"code":175,"codeName":"QueryPlanKilled","errmsg":"collection renamed from \'${nss}\' to \'${dropPendingNss}\'. UUID ${uuid}`';
|
|
} else {
|
|
expectedLogFor6and8 =
|
|
"`Sync process retrying CollectionCloner stage query due to QueryPlanKilled: collection renamed from '${nss}' to '${dropPendingNss}'. UUID ${uuid}`";
|
|
}
|
|
}
|
|
|
|
jsTestLog("[6] Testing cross-DB rename between getMores.");
|
|
runRenameTest({
|
|
failPoint: "initialSyncHangCollectionClonerAfterHandlingBatchResponse",
|
|
secondaryStartupParams: {collectionClonerBatchSize: 1},
|
|
renameAcrossDBs: true,
|
|
expectedLog: expectedLogFor6and8
|
|
});
|
|
|
|
jsTestLog("[7] Testing rename with new same-name collection created, between getMores.");
|
|
runRenameTest({
|
|
failPoint: "initialSyncHangCollectionClonerAfterHandlingBatchResponse",
|
|
secondaryStartupParams: {collectionClonerBatchSize: 1},
|
|
expectedLog: expectedLogFor5and7
|
|
});
|
|
|
|
jsTestLog("[8] Testing cross-DB rename with new same-name collection created, between getMores.");
|
|
runRenameTest({
|
|
failPoint: "initialSyncHangCollectionClonerAfterHandlingBatchResponse",
|
|
secondaryStartupParams: {collectionClonerBatchSize: 1},
|
|
renameAcrossDBs: true,
|
|
expectedLog: expectedLogFor6and8
|
|
});
|
|
|
|
replTest.stopSet();
|
|
})(); |