summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosef Ahmad <josef.ahmad@mongodb.com>2021-12-02 08:05:03 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-12-17 11:09:03 +0000
commit9782d57c70e84cc7e1f52d6d7cbb60a2bb245654 (patch)
treeec65dd65fa3e135e82036fd3fb2ad7f750e3edb3
parentc6245395496606cdb280c6cf5c5509258fb33841 (diff)
downloadmongo-9782d57c70e84cc7e1f52d6d7cbb60a2bb245654.tar.gz
SERVER-61738 Revive dbcheck.js and make it deterministic
(cherry picked from commit c5f47b7a04ce688b088c1373805e876b0debae66)
-rw-r--r--etc/backports_required_for_multiversion_tests.yml2
-rw-r--r--jstests/replsets/dbcheck.js210
2 files changed, 115 insertions, 97 deletions
diff --git a/etc/backports_required_for_multiversion_tests.yml b/etc/backports_required_for_multiversion_tests.yml
index 0aba4542b97..a3a01005a02 100644
--- a/etc/backports_required_for_multiversion_tests.yml
+++ b/etc/backports_required_for_multiversion_tests.yml
@@ -185,6 +185,8 @@ all:
test_file: jstests/sharding/coordinate_txn_commit_with_tickets_exhausted.js
- ticket: SERVER-60685
test_file: jstests/sharding/cancel_coordinate_txn_commit_with_tickets_exhausted.js
+ - ticket: SERVER-61738
+ test_file: jstests/replsets/dbcheck.js
suites:
diff --git a/jstests/replsets/dbcheck.js b/jstests/replsets/dbcheck.js
index 99ab6261b00..3cdc784d6f2 100644
--- a/jstests/replsets/dbcheck.js
+++ b/jstests/replsets/dbcheck.js
@@ -1,18 +1,21 @@
/**
- * dbcheck.js
- *
* Test the dbCheck command.
+ *
+ * @tags: [
+ * # We need persistence as we temporarily restart nodes as standalones.
+ * requires_persistence,
+ * assumes_against_mongod_not_mongos,
+ * ]
*/
(function() {
"use strict";
-// TODO(SERVER-31323): Re-enable when existing dbCheck issues are fixed.
-if (true)
- return;
+// This test injects inconsistencies between replica set members; do not fail because of expected
+// dbHash differences.
+TestData.skipCheckDBHashes = true;
-let nodeCount = 3;
-let replSet = new ReplSetTest({name: "dbCheckSet", nodes: nodeCount});
+let replSet = new ReplSetTest({name: "dbCheckSet", nodes: 2});
replSet.startSet();
replSet.initiate();
@@ -50,17 +53,44 @@ function dbCheckCompleted(db) {
return db.currentOp().inprog.filter(x => x["desc"] == "dbCheck")[0] === undefined;
}
+// Wait for DeferredWriter writes to local.system.healthlog to eventually complete.
+// Requires clearLog() before the test case is run.
+// TODO SERVER-61765 remove this function altoghether when healthlogging becomes
+// synchronous.
+function dbCheckHealthLogCompleted(db, coll, maxKey, maxSize, maxCount) {
+ let query = {"namespace": coll.getFullName(), "operation": "dbCheckBatch"};
+ if (maxSize === undefined && maxCount === undefined && maxKey === undefined) {
+ query['data.maxKey'] = {"$type": "maxKey"};
+ }
+ if (maxCount !== undefined) {
+ query['data.count'] = maxCount;
+ } else {
+ if (maxSize !== undefined) {
+ query['data.bytes'] = maxSize;
+ } else {
+ if (maxKey !== undefined) {
+ query['data.maxKey'] = maxKey;
+ }
+ }
+ }
+ return db.getSiblingDB("local").system.healthlog.find(query).itcount() === 1;
+}
+
// Wait for dbCheck to complete (on both primaries and secondaries). Fails an assertion if
// dbCheck takes longer than maxMs.
-function awaitDbCheckCompletion(db) {
+function awaitDbCheckCompletion(db, collName, maxKey, maxSize, maxCount) {
let start = Date.now();
assert.soon(() => dbCheckCompleted(db), "dbCheck timed out");
replSet.awaitSecondaryNodes();
replSet.awaitReplication();
- // Give the health log buffers some time to flush.
- sleep(100);
+ forEachNode(function(node) {
+ const nodeDB = node.getDB(db);
+ const nodeColl = node.getDB(db)[collName];
+ assert.soon(() => dbCheckHealthLogCompleted(nodeDB, nodeColl, maxKey, maxSize, maxCount),
+ "dbCheck wait for health log timed out");
+ });
}
// Check that everything in the health log shows a successful and complete check with no found
@@ -158,7 +188,7 @@ function simpleTestConsistent() {
let db = master.getDB(dbName);
assert.commandWorked(db.runCommand({"dbCheck": multiBatchSimpleCollName}));
- awaitDbCheckCompletion(db);
+ awaitDbCheckCompletion(db, multiBatchSimpleCollName);
checkLogAllConsistent(master);
checkTotalCounts(master, db[multiBatchSimpleCollName]);
@@ -188,7 +218,7 @@ function concurrentTestConsistent() {
coll.deleteOne({});
}
- awaitDbCheckCompletion(db);
+ awaitDbCheckCompletion(db, collName);
checkLogAllConsistent(master);
// Omit check for total counts, which might have changed with concurrent updates.
@@ -239,7 +269,7 @@ function testDbCheckParameters() {
assert.commandWorked(
db.runCommand({dbCheck: multiBatchSimpleCollName, minKey: start, maxKey: end}));
- awaitDbCheckCompletion(db);
+ awaitDbCheckCompletion(db, multiBatchSimpleCollName, end);
checkEntryBounds(start, end);
@@ -253,7 +283,7 @@ function testDbCheckParameters() {
{dbCheck: multiBatchSimpleCollName, minKey: start, maxKey: end, maxCount: maxCount}));
// We expect it to reach the count limit before reaching maxKey.
- awaitDbCheckCompletion(db);
+ awaitDbCheckCompletion(db, multiBatchSimpleCollName, undefined, undefined, maxCount);
checkEntryBounds(start, start + maxCount);
// Finally, do the same with a size constraint.
@@ -261,7 +291,7 @@ function testDbCheckParameters() {
let maxSize = maxCount * docSize;
assert.commandWorked(db.runCommand(
{dbCheck: multiBatchSimpleCollName, minKey: start, maxKey: end, maxSize: maxSize}));
- awaitDbCheckCompletion(db);
+ awaitDbCheckCompletion(db, multiBatchSimpleCollName, end, maxSize);
checkEntryBounds(start, start + maxCount);
}
@@ -332,110 +362,96 @@ function testSucceedsOnStepdown() {
testSucceedsOnStepdown();
-function collectionUuid(db, collName) {
- return db.getCollectionInfos().filter(coll => coll.name === collName)[0].info.uuid;
-}
-
-function getDummyOplogEntry() {
- let master = replSet.getPrimary();
- let coll = master.getDB(dbName)[collName];
-
- let replSetStatus =
- assert.commandWorked(master.getDB("admin").runCommand({replSetGetStatus: 1}));
- let connStatus = replSetStatus.members.filter(m => m.self)[0];
- let lastOpTime = connStatus.optime;
-
- let entry = master.getDB("local").oplog.rs.find().sort({$natural: -1})[0];
- entry["ui"] = collectionUuid(master.getDB(dbName), collName);
- entry["ns"] = coll.stats().ns;
- entry["ts"] = new Timestamp();
-
- return entry;
-}
-
-// Create various inconsistencies, and check that dbCheck spots them.
-function insertOnSecondaries(doc) {
- let master = replSet.getPrimary();
- let entry = getDummyOplogEntry();
- entry["op"] = "i";
- entry["o"] = doc;
-
- master.getDB("local").oplog.rs.insertOne(entry);
-}
-
-// Run an apply-ops-ish command on a secondary.
-function runCommandOnSecondaries(doc, ns) {
- let master = replSet.getPrimary();
- let entry = getDummyOplogEntry();
- entry["op"] = "c";
- entry["o"] = doc;
-
- if (ns !== undefined) {
- entry["ns"] = ns;
- }
+// Temporarily restart the secondary as a standalone, inject an inconsistency and
+// restart it back as a secondary.
+function injectInconsistencyOnSecondary(cmd) {
+ const secondaryConn = replSet.getSecondary();
+ const secondaryNodeId = replSet.getNodeId(secondaryConn);
+ replSet.stop(secondaryNodeId, {forRestart: true /* preserve dbPath */});
- master.getDB("local").oplog.rs.insertOne(entry);
-}
+ const standaloneConn = MongoRunner.runMongod({
+ dbpath: secondaryConn.dbpath,
+ noCleanData: true,
+ });
-// And on a primary.
-function runCommandOnPrimary(doc) {
- let master = replSet.getPrimary();
- let entry = getDummyOplogEntry();
- entry["op"] = "c";
- entry["o"] = doc;
+ const standaloneDB = standaloneConn.getDB(dbName);
+ assert.commandWorked(standaloneDB.runCommand(cmd));
- master.getDB("admin").runCommand({applyOps: [entry]});
+ // Shut down the secondary and restart it as a member of the replica set.
+ MongoRunner.stopMongod(standaloneConn);
+ replSet.start(secondaryNodeId, {}, true /*restart*/);
+ replSet.awaitNodesAgreeOnPrimary();
}
// Just add an extra document, and test that it catches it.
function simpleTestCatchesExtra() {
- let master = replSet.getPrimary();
- let db = master.getDB(dbName);
-
- clearLog();
+ {
+ const primary = replSet.getPrimary();
+ const db = primary.getDB(dbName);
+ db[collName].drop();
+ clearLog();
+
+ // Create the collection on the primary.
+ db.createCollection(collName, {validationLevel: "off"});
+ }
- insertOnSecondaries({_id: 12390290});
+ replSet.awaitReplication();
+ injectInconsistencyOnSecondary({insert: collName, documents: [{}]});
+ replSet.awaitReplication();
- assert.commandWorked(db.runCommand({dbCheck: collName}));
- awaitDbCheckCompletion(db);
+ {
+ const primary = replSet.getPrimary();
+ const db = primary.getDB(dbName);
- let nErrors = replSet.getSecondary()
- .getDB("local")
- .system.healthlog.find({operation: /dbCheck.*/, severity: "error"})
- .count();
+ assert.commandWorked(db.runCommand({dbCheck: collName}));
+ awaitDbCheckCompletion(db, collName);
+ }
+ const errors = replSet.getSecondary().getDB("local").system.healthlog.find(
+ {operation: /dbCheck.*/, severity: "error"});
- assert.neq(nErrors, 0, "dbCheck found no errors after insertion on secondaries");
- assert.eq(nErrors, 1, "dbCheck found too many errors after single inconsistent insertion");
+ assert.eq(errors.count(),
+ 1,
+ "expected exactly 1 inconsistency after single inconsistent insertion, found: " +
+ JSON.stringify(errors.toArray()));
}
-// Test that dbCheck catches changing various pieces of collection metadata.
+// Test that dbCheck catches an extra index on the secondary.
function testCollectionMetadataChanges() {
- let master = replSet.getPrimary();
- let db = master.getDB(dbName);
- db[collName].drop();
- clearLog();
-
- // Create the collection on the primary.
- db.createCollection(collName, {validationLevel: "off"});
+ {
+ const primary = replSet.getPrimary();
+ const db = primary.getDB(dbName);
+ db[collName].drop();
+ clearLog();
+
+ // Create the collection on the primary.
+ db.createCollection(collName, {validationLevel: "off"});
+ }
- // Add an index on the secondaries.
- runCommandOnSecondaries({createIndexes: collName, v: 2, key: {"foo": 1}, name: "foo_1"},
- dbName + ".$cmd");
+ replSet.awaitReplication();
+ injectInconsistencyOnSecondary(
+ {createIndexes: collName, indexes: [{key: {whatever: 1}, name: "whatever"}]});
+ replSet.awaitReplication();
- assert.commandWorked(db.runCommand({dbCheck: collName}));
- awaitDbCheckCompletion(db);
+ {
+ const primary = replSet.getPrimary();
+ const db = primary.getDB(dbName);
+ assert.commandWorked(db.runCommand({dbCheck: collName}));
+ awaitDbCheckCompletion(db, collName);
+ }
- let nErrors = replSet.getSecondary()
- .getDB("local")
- .system.healthlog
- .find({"operation": /dbCheck.*/, "severity": "error", "data.success": true})
- .count();
+ const errors = replSet.getSecondary().getDB("local").system.healthlog.find(
+ {"operation": /dbCheck.*/, "severity": "error", "data.success": true});
- assert.eq(nErrors, 1, "dbCheck found wrong number of errors after inconsistent `create`");
+ assert.eq(errors.count(),
+ 1,
+ "expected exactly 1 inconsistency after single inconsistent index creation, found: " +
+ JSON.stringify(errors.toArray()));
clearLog();
}
simpleTestCatchesExtra();
testCollectionMetadataChanges();
+
+replSet.stopSet();
})();