mongo/jstests/replsets/dbcheck/dbcheck_validate_bson.js

269 lines
9.6 KiB
JavaScript

/**
* Test BSON validation in the dbCheck command.
*
* @tags: [
* requires_fcv_80,
* corrupts_data,
* ]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {
checkHealthLog,
clearHealthLog,
forEachNonArbiterNode,
resetAndInsert,
runDbCheck,
} from "jstests/replsets/libs/dbcheck_utils.js";
// Skipping data consistency checks because invalid BSON is inserted into primary and secondary
// separately.
TestData.skipCollectionAndIndexValidation = true;
TestData.skipCheckDBHashes = true;
const dbName = "dbCheckBSONValidation";
const collName = "dbCheckBSONValidation-collection";
const replSet = new ReplSetTest({
name: jsTestName(),
nodes: 2,
nodeOptions: {setParameter: {dbCheckHealthLogEveryNBatches: 1}},
});
replSet.startSet();
replSet.initiate();
const primary = replSet.getPrimary();
const primaryDb = primary.getDB(dbName);
const secondary = replSet.getSecondary();
const secondaryDb = secondary.getDB(dbName);
const nDocs = 10;
const maxDocsPerBatch = 2;
const errQuery = {
operation: "dbCheckBatch",
severity: "error",
};
const invalidBSONQuery = {
operation: "dbCheckBatch",
severity: "error",
msg: "Document is not well-formed BSON",
"data.context.recordID": {$exists: true},
};
const invalidHashQuery = {
operation: "dbCheckBatch",
severity: "error",
msg: "dbCheck batch inconsistent",
"data.md5": {$exists: true},
};
const warningQuery = {
operation: "dbCheckBatch",
severity: "warning",
};
const BSONWarningQuery = {
operation: "dbCheckBatch",
severity: "warning",
msg: "Document is not well-formed BSON",
};
const successfulBatchQuery = {
operation: "dbCheckBatch",
severity: "info",
msg: "dbCheck batch consistent",
"data.count": maxDocsPerBatch,
};
const errAndWarningQuery = {
operation: "dbCheckBatch",
$or: [{severity: "error"}, {severity: "warning"}],
};
const doc1 = {
_id: 0,
a: 1,
};
// The default WC is majority and godinsert command on a secondary is incompatible with
// wc:majority.
assert.commandWorked(
primary.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}, writeConcern: {w: "majority"}}),
);
// Insert corrupt document for testing via failpoint.
const insertCorruptDocument = function (db, collName) {
assert.commandWorked(db.adminCommand({configureFailPoint: "corruptDocumentOnInsert", mode: "alwaysOn"}));
// Use godinsert to insert into the node directly.
assert.commandWorked(db.runCommand({godinsert: collName, obj: doc1}));
assert.commandWorked(db.adminCommand({configureFailPoint: "corruptDocumentOnInsert", mode: "off"}));
};
function testKDefaultBSONValidation() {
jsTestLog("Testing that dbCheck logs error in health log when node has kDefault invalid BSON.");
clearHealthLog(replSet);
// Insert invalid BSON that fails the kDefault check into both nodes in the replica set.
assert.commandWorked(primaryDb.createCollection(collName));
replSet.awaitReplication();
forEachNonArbiterNode(replSet, function (node) {
insertCorruptDocument(node.getDB(dbName), collName);
});
// Both primary and secondary should have error in health log for invalid BSON.
runDbCheck(
replSet,
primaryDb,
collName,
{validateMode: "dataConsistencyAndMissingIndexKeysCheck"},
true /* awaitCompletion */,
);
// Primary and secondary have the same invalid BSON document so there is an error for invalid
// BSON but not data inconsistency. We check that the only errors in the health log are for
// invalid BSON.
checkHealthLog(primary.getDB("local").system.healthlog, invalidBSONQuery, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, invalidBSONQuery, 1);
checkHealthLog(primary.getDB("local").system.healthlog, errQuery, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, errQuery, 1);
}
function testPrimaryInvalidBson() {
jsTestLog("Testing when the primary has invalid BSON but the secondary does not.");
clearHealthLog(replSet);
primaryDb[collName].drop();
assert.commandWorked(primaryDb.createCollection(collName));
replSet.awaitReplication();
// Insert an invalid document on the primary.
insertCorruptDocument(primaryDb, collName);
// Insert a normal document on the secondary.
assert.commandWorked(secondaryDb.runCommand({godinsert: collName, obj: doc1}));
runDbCheck(
replSet,
primaryDb,
collName,
{maxDocsPerBatch: maxDocsPerBatch, validateMode: "dataConsistencyAndMissingIndexKeysCheck"},
true /* awaitCompletion */,
);
// Verify primary logs an error for invalid BSON while the secondary logs an error for data
// inconsistency.
checkHealthLog(primary.getDB("local").system.healthlog, invalidBSONQuery, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, invalidHashQuery, 1);
// Verify that the primary and secondary do not have other error/warning logs.
checkHealthLog(primary.getDB("local").system.healthlog, errAndWarningQuery, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, errAndWarningQuery, 1);
}
function testSecondaryInvalidBson() {
jsTestLog("Testing when the secondary has invalid BSON but the primary does not.");
clearHealthLog(replSet);
primaryDb[collName].drop();
assert.commandWorked(primaryDb.createCollection(collName));
replSet.awaitReplication();
// Insert a normal document on the primary.
assert.commandWorked(primaryDb.runCommand({godinsert: collName, obj: doc1}));
// Insert an invalid document on the secondary.
insertCorruptDocument(secondaryDb, collName);
runDbCheck(
replSet,
primaryDb,
collName,
{maxDocsPerBatch: maxDocsPerBatch, validateMode: "dataConsistencyAndMissingIndexKeysCheck"},
true /* awaitCompletion */,
);
// Verify secondary logs an error for invalid BSON and data inconsistency.
checkHealthLog(secondary.getDB("local").system.healthlog, invalidBSONQuery, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, invalidHashQuery, 1);
// Verify that the primary and secondary do not have other error/warning logs.
checkHealthLog(primary.getDB("local").system.healthlog, errAndWarningQuery, 0);
checkHealthLog(secondary.getDB("local").system.healthlog, errAndWarningQuery, 2);
}
function testMultipleBatches() {
jsTestLog("Testing that invalid BSON check works across multiple batches.");
clearHealthLog(replSet);
// Primary contains 10 valid documents, secondary contains 9 valid and 1 invalid document.
resetAndInsert(replSet, primaryDb, collName, nDocs - 1);
assert.commandWorked(primaryDb.runCommand({godinsert: collName, obj: {_id: 0, a: nDocs}}));
insertCorruptDocument(secondaryDb, collName);
runDbCheck(
replSet,
primaryDb,
collName,
{maxDocsPerBatch: maxDocsPerBatch, validateMode: "dataConsistencyAndMissingIndexKeysCheck"},
true /* awaitCompletion */,
);
// Secondary logs an error for invalid BSON and data inconsistency.
checkHealthLog(secondary.getDB("local").system.healthlog, invalidBSONQuery, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, invalidHashQuery, 1);
// Primary and secondary do not terminate after first batch and finish dbCheck successfully.
checkHealthLog(primary.getDB("local").system.healthlog, successfulBatchQuery, nDocs / maxDocsPerBatch);
checkHealthLog(secondary.getDB("local").system.healthlog, successfulBatchQuery, nDocs / maxDocsPerBatch - 1);
// There should be no other error queries.
checkHealthLog(primary.getDB("local").system.healthlog, errQuery, 0);
checkHealthLog(secondary.getDB("local").system.healthlog, errQuery, 2);
checkHealthLog(primary.getDB("local").system.healthlog, {operation: "dbCheckStop"}, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, {operation: "dbCheckStop"}, 1);
}
function testInvalidUuid() {
jsTestLog(
"Testing that a BSON document that is structurally valid but invalid in other ways (such as by having UUID that have incorrect lengths) is included in hashing but logs a warning",
);
clearHealthLog(replSet);
primaryDb[collName].drop();
assert.commandWorked(primaryDb.createCollection(collName));
replSet.awaitReplication();
// Insert 2 documents with invalid UUID (length is 4 or 20 instead of 16).
assert.commandWorked(primaryDb[collName].insert({u: HexData(4, "deadbeef")}));
assert.commandWorked(primaryDb[collName].insert({u: HexData(4, "deadbeef".repeat(5))}));
replSet.awaitReplication();
runDbCheck(
replSet,
primaryDb,
collName,
{
maxDocsPerBatch: maxDocsPerBatch,
validateMode: "dataConsistencyAndMissingIndexKeysCheck",
bsonValidateMode: "kFull",
},
true /* awaitCompletion */,
);
// Verify both primary and secondary log a warning for invalid BSON (k).
checkHealthLog(primary.getDB("local").system.healthlog, BSONWarningQuery, 2);
checkHealthLog(secondary.getDB("local").system.healthlog, BSONWarningQuery, 2);
// Verify that the primary and secondary do not have other error/warning logs.
checkHealthLog(primary.getDB("local").system.healthlog, successfulBatchQuery, 1);
checkHealthLog(secondary.getDB("local").system.healthlog, successfulBatchQuery, 1);
checkHealthLog(primary.getDB("local").system.healthlog, errAndWarningQuery, 2);
checkHealthLog(secondary.getDB("local").system.healthlog, errAndWarningQuery, 2);
}
testKDefaultBSONValidation();
testPrimaryInvalidBson();
testSecondaryInvalidBson();
testMultipleBatches();
testInvalidUuid();
replSet.stopSet();