summaryrefslogtreecommitdiff
path: root/src/mongo/shell
diff options
context:
space:
mode:
authorXueruiFa <xuerui.fa@mongodb.com>2020-11-17 21:20:53 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-12-15 15:09:12 +0000
commitc1889f64dc7599e4274b9423c3b4a61e5bb30ac5 (patch)
treef899c6a0e149c6d537f02bb8c78693d6ae416b4f /src/mongo/shell
parent53e6612ae87275817a5b4d0fb6e9822a4088f1e8 (diff)
downloadmongo-c1889f64dc7599e4274b9423c3b4a61e5bb30ac5.tar.gz
SERVER-50935: Expand dbhash checks to ensure two replica sets are in sync for a given tenantId
Diffstat (limited to 'src/mongo/shell')
-rw-r--r--src/mongo/shell/data_consistency_checker.js267
-rw-r--r--src/mongo/shell/replsettest.js230
2 files changed, 260 insertions, 237 deletions
diff --git a/src/mongo/shell/data_consistency_checker.js b/src/mongo/shell/data_consistency_checker.js
index b3a4f9153a9..8fb7be3c634 100644
--- a/src/mongo/shell/data_consistency_checker.js
+++ b/src/mongo/shell/data_consistency_checker.js
@@ -177,47 +177,262 @@ var {DataConsistencyChecker} = (function() {
return {docsWithDifferentContents, docsMissingOnFirst, docsMissingOnSecond};
}
- static dumpCollectionDiff(
- rst, collectionPrinted, primaryCollInfos, secondaryCollInfos, collName) {
- print('Dumping collection: ' + primaryCollInfos.ns(collName));
+ static getCollectionDiffUsingSessions(sourceSession,
+ syncingSession,
+ dbName,
+ collNameOrUUID) {
+ const sourceDB = sourceSession.getDatabase(dbName);
+ const syncingDB = syncingSession.getDatabase(dbName);
+
+ const commandObj = {find: collNameOrUUID, sort: {_id: 1}};
+ const sourceCursor = new DBCommandCursor(sourceDB, sourceDB.runCommand(commandObj));
+ const syncingCursor = new DBCommandCursor(syncingDB, syncingDB.runCommand(commandObj));
+ const diff = this.getDiff(sourceCursor, syncingCursor);
+
+ return {
+ docsWithDifferentContents: diff.docsWithDifferentContents.map(
+ ({first, second}) => ({sourceNode: first, syncingNode: second})),
+ docsMissingOnSource: diff.docsMissingOnFirst,
+ docsMissingOnSyncing: diff.docsMissingOnSecond
+ };
+ }
- const primaryExists = primaryCollInfos.print(collectionPrinted, collName);
- const secondaryExists = secondaryCollInfos.print(collectionPrinted, collName);
+ static dumpCollectionDiff(collectionPrinted, sourceCollInfos, syncingCollInfos, collName) {
+ print('Dumping collection: ' + sourceCollInfos.ns(collName));
- if (!primaryExists || !secondaryExists) {
+ const sourceExists = sourceCollInfos.print(collectionPrinted, collName);
+ const syncingExists = syncingCollInfos.print(collectionPrinted, collName);
+
+ if (!sourceExists || !syncingExists) {
print(`Skipping checking collection differences for ${
- primaryCollInfos.ns(
- collName)} since it does not exist on primary and secondary`);
+ sourceCollInfos.ns(collName)} since it does not exist on both nodes`);
return;
}
- const primary = primaryCollInfos.conn;
- const secondary = secondaryCollInfos.conn;
+ const sourceNode = sourceCollInfos.conn;
+ const syncingNode = syncingCollInfos.conn;
- const primarySession = primary.getDB('test').getSession();
- const secondarySession = secondary.getDB('test').getSession();
- const diff = rst.getCollectionDiffUsingSessions(
- primarySession, secondarySession, primaryCollInfos.dbName, collName);
+ const sourceSession = sourceNode.getDB('test').getSession();
+ const syncingSession = syncingNode.getDB('test').getSession();
+ const diff = this.getCollectionDiffUsingSessions(
+ sourceSession, syncingSession, sourceCollInfos.dbName, collName);
for (let {
- primary: primaryDoc,
- secondary: secondaryDoc,
+ sourceNode: sourceDoc,
+ syncingNode: syncingDoc,
} of diff.docsWithDifferentContents) {
- print(`Mismatching documents between the primary ${primary.host}` +
- ` and the secondary ${secondary.host}:`);
- print(' primary: ' + tojsononeline(primaryDoc));
- print(' secondary: ' + tojsononeline(secondaryDoc));
+ print(`Mismatching documents between the source node ${sourceNode.host}` +
+ ` and the syncing node ${syncingNode.host}:`);
+ print(' sourceNode: ' + tojsononeline(sourceDoc));
+ print(' syncingNode: ' + tojsononeline(syncingDoc));
+ }
+
+ if (diff.docsMissingOnSource.length > 0) {
+ print(`The following documents are missing on the source node ${sourceNode.host}:`);
+ print(diff.docsMissingOnSource.map(doc => tojsononeline(doc)).join('\n'));
}
- if (diff.docsMissingOnPrimary.length > 0) {
- print(`The following documents are missing on the primary ${primary.host}:`);
- print(diff.docsMissingOnPrimary.map(doc => tojsononeline(doc)).join('\n'));
+ if (diff.docsMissingOnSyncing.length > 0) {
+ print(
+ `The following documents are missing on the syncing node ${syncingNode.host}:`);
+ print(diff.docsMissingOnSyncing.map(doc => tojsononeline(doc)).join('\n'));
}
+ }
- if (diff.docsMissingOnSecondary.length > 0) {
- print(`The following documents are missing on the secondary ${secondary.host}:`);
- print(diff.docsMissingOnSecondary.map(doc => tojsononeline(doc)).join('\n'));
+ static checkDBHash(sourceDBHash,
+ sourceCollInfos,
+ syncingDBHash,
+ syncingCollInfos,
+ msgPrefix,
+ ignoreUUIDs,
+ syncingHasIndexes,
+ collectionPrinted) {
+ let success = true;
+
+ const sourceDBName = sourceCollInfos.dbName;
+ const syncingDBName = syncingCollInfos.dbName;
+ assert.eq(
+ sourceDBName,
+ syncingDBName,
+ `dbName was not the same: source: ${sourceDBName}, syncing: ${syncingDBName}`);
+ const dbName = syncingDBName;
+
+ const sourceCollections = Object.keys(sourceDBHash.collections);
+ const syncingCollections = Object.keys(syncingDBHash.collections);
+
+ const dbHashesMsg =
+ `source: ${tojson(sourceDBHash)}, syncing: ${tojson(syncingDBHash)}`;
+ const prettyPrint = (outputMsg => {
+ print(`${msgPrefix}, ${outputMsg}`);
+ });
+
+ const arraySymmetricDifference = ((a, b) => {
+ const inAOnly = a.filter(function(elem) {
+ return b.indexOf(elem) < 0;
+ });
+
+ const inBOnly = b.filter(function(elem) {
+ return a.indexOf(elem) < 0;
+ });
+
+ return inAOnly.concat(inBOnly);
+ });
+
+ if (sourceCollections.length !== syncingCollections.length) {
+ prettyPrint(`the two nodes have a different number of collections: ${dbHashesMsg}`);
+ for (const diffColl of arraySymmetricDifference(sourceCollections,
+ syncingCollections)) {
+ this.dumpCollectionDiff(
+ collectionPrinted, sourceCollInfos, syncingCollInfos, diffColl);
+ }
+ success = false;
}
+
+ const nonCappedCollNames = sourceCollInfos.getNonCappedCollNames();
+ // Only compare the dbhashes of non-capped collections because capped
+ // collections are not necessarily truncated at the same points between the source and
+ // syncing nodes.
+ nonCappedCollNames.forEach(collName => {
+ if (sourceDBHash.collections[collName] !== syncingDBHash.collections[collName]) {
+ prettyPrint(`the two nodes have a different hash for the collection ${dbName}.${
+ collName}: ${dbHashesMsg}`);
+ this.dumpCollectionDiff(
+ collectionPrinted, sourceCollInfos, syncingCollInfos, collName);
+ success = false;
+ }
+ });
+
+ syncingCollInfos.collInfosRes.forEach(syncingInfo => {
+ sourceCollInfos.collInfosRes.forEach(sourceInfo => {
+ if (syncingInfo.name === sourceInfo.name &&
+ syncingInfo.type === sourceInfo.type) {
+ if (ignoreUUIDs) {
+ prettyPrint(`skipping UUID check for ${[sourceInfo.name]}`);
+ sourceInfo.info.uuid = null;
+ syncingInfo.info.uuid = null;
+ }
+
+ // Ignore the 'flags' collection option as it was removed in 4.2
+ sourceInfo.options.flags = null;
+ syncingInfo.options.flags = null;
+
+ // Ignore the 'ns' field in the 'idIndex' field as 'ns' was removed
+ // from index specs in 4.4.
+ if (sourceInfo.idIndex) {
+ delete sourceInfo.idIndex.ns;
+ delete syncingInfo.idIndex.ns;
+ }
+
+ if (!bsonBinaryEqual(syncingInfo, sourceInfo)) {
+ prettyPrint(
+ `the two nodes have different attributes for the collection or view ${
+ dbName}.${syncingInfo.name}`);
+ this.dumpCollectionDiff(collectionPrinted,
+ sourceCollInfos,
+ syncingCollInfos,
+ syncingInfo.name);
+ success = false;
+ }
+ }
+ });
+ });
+
+ // Treats each array as a set and returns true if the contents match. Assumes
+ // the contents of each array are unique.
+ const compareSets = function(leftArr, rightArr) {
+ if (leftArr === undefined) {
+ return rightArr === undefined;
+ }
+
+ if (rightArr === undefined) {
+ return false;
+ }
+
+ const map = {};
+ leftArr.forEach(key => {
+ map[key] = 1;
+ });
+
+ rightArr.forEach(key => {
+ if (map[key] === undefined) {
+ map[key] = -1;
+ } else {
+ delete map[key];
+ }
+ });
+
+ // The map is empty when both sets match.
+ for (let key in map) {
+ if (map.hasOwnProperty(key)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ const sourceNode = sourceCollInfos.conn;
+ const syncingNode = syncingCollInfos.conn;
+
+ // Check that the following collection stats are the same between the source and syncing
+ // nodes:
+ // capped
+ // nindexes, except on nodes with buildIndexes: false
+ // ns
+ sourceCollections.forEach(collName => {
+ const sourceCollStats = sourceNode.getDB(dbName).runCommand({collStats: collName});
+ const syncingCollStats =
+ syncingNode.getDB(dbName).runCommand({collStats: collName});
+
+ if (sourceCollStats.ok !== 1 || syncingCollStats.ok !== 1) {
+ sourceCollInfos.print(collectionPrinted, collName);
+ syncingCollInfos.print(collectionPrinted, collName);
+ success = false;
+ return;
+ }
+
+ // Provide hint on where to look within stats.
+ let reasons = [];
+ if (sourceCollStats.capped !== syncingCollStats.capped) {
+ reasons.push('capped');
+ }
+
+ if (sourceCollStats.ns !== syncingCollStats.ns) {
+ reasons.push('ns');
+ }
+
+ if (syncingHasIndexes && sourceCollStats.nindexes !== syncingCollStats.nindexes) {
+ reasons.push('indexes');
+ }
+
+ const indexBuildsMatch =
+ compareSets(sourceCollStats.indexBuilds, syncingCollStats.indexBuilds);
+ if (syncingHasIndexes && !indexBuildsMatch) {
+ reasons.push('indexBuilds');
+ }
+
+ if (reasons.length === 0) {
+ return;
+ }
+
+ prettyPrint(`the two nodes have different states for the collection ${dbName}.${
+ collName}: ${reasons.join(', ')}`);
+ this.dumpCollectionDiff(
+ collectionPrinted, sourceCollInfos, syncingCollInfos, collName);
+ success = false;
+ });
+
+ if (nonCappedCollNames.length === sourceCollections.length) {
+ // If the two nodes have the same hashes for all the
+ // collections in the database and there aren't any capped collections,
+ // then the hashes for the whole database should match.
+ if (sourceDBHash.md5 !== syncingDBHash.md5) {
+ prettyPrint(`the two nodes have a different has for the ${dbName} database: ${
+ dbHashesMsg}`);
+ success = false;
+ }
+ }
+
+ return success;
}
}
diff --git a/src/mongo/shell/replsettest.js b/src/mongo/shell/replsettest.js
index d599a37ec21..9cd3a76e8b6 100644
--- a/src/mongo/shell/replsettest.js
+++ b/src/mongo/shell/replsettest.js
@@ -2161,25 +2161,6 @@ var ReplSetTest = function(opts) {
});
};
- this.getCollectionDiffUsingSessions = function(
- primarySession, secondarySession, dbName, collNameOrUUID) {
- const primaryDB = primarySession.getDatabase(dbName);
- const secondaryDB = secondarySession.getDatabase(dbName);
-
- const commandObj = {find: collNameOrUUID, sort: {_id: 1}};
- const primaryCursor = new DBCommandCursor(primaryDB, primaryDB.runCommand(commandObj));
- const secondaryCursor =
- new DBCommandCursor(secondaryDB, secondaryDB.runCommand(commandObj));
- const diff = DataConsistencyChecker.getDiff(primaryCursor, secondaryCursor);
-
- return {
- docsWithDifferentContents: diff.docsWithDifferentContents.map(
- ({first, second}) => ({primary: first, secondary: second})),
- docsMissingOnPrimary: diff.docsMissingOnFirst,
- docsMissingOnSecondary: diff.docsMissingOnSecond
- };
- };
-
// Gets the dbhash for the current primary and for all secondaries (or the members of
// 'secondaries', if specified).
this.getHashes = function(dbName, secondaries) {
@@ -2190,7 +2171,7 @@ var ReplSetTest = function(opts) {
secondaries = secondaries || _determineLiveSecondaries();
const sessions = [
- this._primary,
+ self._primary,
...secondaries.filter(conn => {
return !conn.adminCommand({isMaster: 1}).arbiterOnly;
})
@@ -2296,18 +2277,6 @@ var ReplSetTest = function(opts) {
var collectionPrinted = new Set();
- function arraySymmetricDifference(a, b) {
- var inAOnly = a.filter(function(elem) {
- return b.indexOf(elem) < 0;
- });
-
- var inBOnly = b.filter(function(elem) {
- return a.indexOf(elem) < 0;
- });
-
- return inAOnly.concat(inBOnly);
- }
-
function checkDBHashesForReplSet(
rst, dbBlacklist = [], secondaries, msgPrefix, ignoreUUIDs) {
// We don't expect the local database to match because some of its
@@ -2315,13 +2284,13 @@ var ReplSetTest = function(opts) {
dbBlacklist.push('local');
secondaries = secondaries || rst._secondaries;
- var success = true;
- var hasDumpedOplog = false;
+ let success = true;
+ let hasDumpedOplog = false;
// Use '_primary' instead of getPrimary() to avoid the detection of a new primary.
// '_primary' must have been populated.
- var primary = rst._primary;
- var combinedDBs = new Set(primary.getDBNames());
+ const primary = rst._primary;
+ let combinedDBs = new Set(primary.getDBNames());
const replSetConfig = rst.getReplSetConfigFromNode();
print("checkDBHashesForReplSet waiting for secondaries to be ready: " +
@@ -2341,7 +2310,7 @@ var ReplSetTest = function(opts) {
node.getDBNames().forEach(dbName => combinedDBs.add(dbName));
});
- for (var dbName of combinedDBs) {
+ for (const dbName of combinedDBs) {
if (Array.contains(dbBlacklist, dbName)) {
continue;
}
@@ -2359,187 +2328,26 @@ var ReplSetTest = function(opts) {
dbHashes.secondaries.forEach(secondaryDBHash => {
assert.commandWorked(secondaryDBHash);
- var secondary = secondaryDBHash._mongo;
- var secondaryCollections = Object.keys(secondaryDBHash.collections);
+ const secondary = secondaryDBHash._mongo;
+ const secondaryCollections = Object.keys(secondaryDBHash.collections);
// Check that collection information is consistent on the primary and
// secondaries.
const secondaryCollInfos = new CollInfos(secondary, 'secondary', dbName);
secondaryCollInfos.filter(secondaryCollections);
- if (primaryCollections.length !== secondaryCollections.length) {
- print(
- msgPrefix +
- ', the primary and secondary have a different number of collections: ' +
- tojson(dbHashes));
- for (var diffColl of arraySymmetricDifference(primaryCollections,
- secondaryCollections)) {
- DataConsistencyChecker.dumpCollectionDiff(this,
- collectionPrinted,
- primaryCollInfos,
- secondaryCollInfos,
- diffColl);
- }
- success = false;
- }
-
- const nonCappedCollNames = primaryCollInfos.getNonCappedCollNames();
- // Only compare the dbhashes of non-capped collections because capped
- // collections are not necessarily truncated at the same points
- // across replica set members.
- nonCappedCollNames.forEach(collName => {
- if (primaryDBHash.collections[collName] !==
- secondaryDBHash.collections[collName]) {
- print(msgPrefix +
- ', the primary and secondary have a different hash for the' +
- ' collection ' + dbName + '.' + collName + ': ' +
- tojson(dbHashes));
- DataConsistencyChecker.dumpCollectionDiff(this,
- collectionPrinted,
- primaryCollInfos,
- secondaryCollInfos,
- collName);
- success = false;
- }
- });
-
- secondaryCollInfos.collInfosRes.forEach(secondaryInfo => {
- primaryCollInfos.collInfosRes.forEach(primaryInfo => {
- if (secondaryInfo.name === primaryInfo.name &&
- secondaryInfo.type === primaryInfo.type) {
- if (ignoreUUIDs) {
- print(msgPrefix + ", skipping UUID check for " +
- primaryInfo.name);
- primaryInfo.info.uuid = null;
- secondaryInfo.info.uuid = null;
- }
-
- // Ignore the 'flags' collection option as it was removed in 4.2
- primaryInfo.options.flags = null;
- secondaryInfo.options.flags = null;
-
- // Ignore the 'ns' field in the 'idIndex' field as 'ns' was removed
- // from index specs in 4.4.
- if (primaryInfo.idIndex) {
- delete primaryInfo.idIndex.ns;
- delete secondaryInfo.idIndex.ns;
- }
-
- if (!bsonBinaryEqual(secondaryInfo, primaryInfo)) {
- print(msgPrefix +
- ', the primary and secondary have different ' +
- 'attributes for the collection or view ' + dbName + '.' +
- secondaryInfo.name);
- DataConsistencyChecker.dumpCollectionDiff(this,
- collectionPrinted,
- primaryCollInfos,
- secondaryCollInfos,
- secondaryInfo.name);
- success = false;
- }
- }
- });
- });
-
- // Treats each array as a set and returns true if the contents match. Assumes
- // the contents of each array are unique.
- const compareSets = function(leftArr, rightArr) {
- if (leftArr === undefined) {
- return rightArr === undefined;
- }
-
- if (rightArr === undefined) {
- return false;
- }
-
- const map = {};
- leftArr.forEach(key => {
- map[key] = 1;
- });
-
- rightArr.forEach(key => {
- if (map[key] === undefined) {
- map[key] = -1;
- } else {
- delete map[key];
- }
- });
-
- // The map is empty when both sets match.
- for (let key in map) {
- if (map.hasOwnProperty(key)) {
- return false;
- }
- }
- return true;
- };
-
- // Check that the following collection stats are the same across replica set
- // members:
- // capped
- // nindexes, except on nodes with buildIndexes: false
- // ns
const hasSecondaryIndexes =
replSetConfig.members[rst.getNodeId(secondary)].buildIndexes !== false;
- primaryCollections.forEach(collName => {
- var primaryCollStats =
- primary.getDB(dbName).runCommand({collStats: collName});
- var secondaryCollStats =
- secondary.getDB(dbName).runCommand({collStats: collName});
-
- if (primaryCollStats.ok !== 1 || secondaryCollStats.ok !== 1) {
- primaryCollInfos.print(collectionPrinted, collName);
- secondaryCollInfos.print(collectionPrinted, collName);
- success = false;
- return;
- }
-
- // Provide hint on where to look within stats.
- let reasons = [];
- if (primaryCollStats.capped !== secondaryCollStats.capped) {
- reasons.push('capped');
- }
-
- if (primaryCollStats.ns !== secondaryCollStats.ns) {
- reasons.push('ns');
- }
- if (hasSecondaryIndexes &&
- primaryCollStats.nindexes !== secondaryCollStats.nindexes) {
- reasons.push('indexes');
- }
-
- const indexBuildsMatch = compareSets(primaryCollStats.indexBuilds,
- secondaryCollStats.indexBuilds);
- if (hasSecondaryIndexes && !indexBuildsMatch) {
- reasons.push('indexBuilds');
- }
-
- if (reasons.length === 0) {
- return;
- }
-
- print(msgPrefix +
- ', the primary and secondary have different stats for the ' +
- 'collection ' + dbName + '.' + collName + ': ' + reasons.join(', '));
- DataConsistencyChecker.dumpCollectionDiff(this,
- collectionPrinted,
- primaryCollInfos,
- secondaryCollInfos,
- collName);
- success = false;
- });
-
- if (nonCappedCollNames.length === primaryCollections.length) {
- // If the primary and secondary have the same hashes for all the
- // collections in the database and there aren't any capped collections,
- // then the hashes for the whole database should match.
- if (primaryDBHash.md5 !== secondaryDBHash.md5) {
- print(msgPrefix +
- ', the primary and secondary have a different hash for ' +
- 'the ' + dbName + ' database: ' + tojson(dbHashes));
- success = false;
- }
- }
+ print(
+ `checking db hash between primary: ${primary} and secondary ${secondary}`);
+ success = DataConsistencyChecker.checkDBHash(primaryDBHash,
+ primaryCollInfos,
+ secondaryDBHash,
+ secondaryCollInfos,
+ msgPrefix,
+ ignoreUUIDs,
+ hasSecondaryIndexes,
+ collectionPrinted);
if (!success) {
if (!hasDumpedOplog) {
@@ -2556,7 +2364,7 @@ var ReplSetTest = function(opts) {
assert(success, 'dbhash mismatch between primary and secondary');
}
- var liveSecondaries = _determineLiveSecondaries();
+ const liveSecondaries = _determineLiveSecondaries();
this.checkReplicaSet(checkDBHashesForReplSet,
liveSecondaries,
this,