0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-30 17:10:48 +01:00
mongodb/jstests/hooks/run_validate_collections_background.js

184 lines
6.5 KiB
JavaScript

/**
* Runs the validate command with {background:true} against all nodes (replica set members and
* standalone nodes, not sharded clusters) concurrently with running tests.
*/
'use strict';
(function() {
load('jstests/libs/discover_topology.js'); // For Topology and DiscoverTopology.
load('jstests/libs/parallelTester.js'); // For Thread.
if (typeof db === 'undefined') {
throw new Error(
"Expected mongo shell to be connected a server, but global 'db' object isn't defined");
}
// Disable implicit sessions so FSM workloads that kill random sessions won't interrupt the
// operations in this test that aren't resilient to interruptions.
TestData.disableImplicitSessions = true;
const conn = db.getMongo();
const topology = DiscoverTopology.findConnectedNodes(conn);
/**
* Returns true if the error code is transient and does not indicate data corruption.
*/
const isIgnorableError = function ignorableError(codeName) {
if (codeName == "NamespaceNotFound" || codeName == "Interrupted" ||
codeName == "CommandNotSupportedOnView" || codeName == "InterruptedAtShutdown" ||
codeName == "InvalidViewDefinition") {
return true;
}
return false;
};
/**
* Runs validate commands with {background:true} against 'host' for all collections it possesses.
*
* Returns the cumulative command failure results, if there are any, in an object
* { ok: 0, error: [{cmd-res}, {cmd-res}, ... ]}
* Or simply OK if all cmds were successful.
* {ok: 1}
*
* This function should not throw if everything is working properly.
*/
const validateCollectionsBackgroundThread = function validateCollectionsBackground(
host, isIgnorableErrorFunc) {
// Calls 'func' with the print() function overridden to be a no-op.
const quietly = (func) => {
const printOriginal = print;
try {
print = Function.prototype;
func();
} finally {
print = printOriginal;
}
};
// Suppress the log messages generated establishing new mongo connections. The
// run_validate_collections_background.js hook is executed frequently by resmoke.py and
// could lead to generating an overwhelming amount of log messages.
let conn;
quietly(() => {
conn = new Mongo(host);
});
assert.neq(null,
conn,
"Failed to connect to host '" + host + "' for background collection validation");
if (!conn.adminCommand("serverStatus").storageEngine.supportsCheckpointCursors) {
print("Skipping background validation against test node: " + host +
" because its storage engine does not support background validation (checkpoints).");
return {ok: 1};
}
// Filter out arbiters.
if (conn.adminCommand({isMaster: 1}).arbiterOnly) {
print("Skipping background validation against test node: " + host +
" because it is an arbiter and has no data.");
return {ok: 1};
}
print("Running background validation on all collections on test node: " + host);
// Save a map of namespace to validate cmd results for any cmds that fail so that we can return
// the results afterwards.
let failedValidateResults = [];
// Validate all collections in every database.
const dbNames =
assert
.commandWorked(conn.adminCommand(
{"listDatabases": 1, "nameOnly": true, "$readPreference": {"mode": "nearest"}}))
.databases.map(function(z) {
return z.name;
});
for (let dbName of dbNames) {
let db = conn.getDB(dbName);
const listCollRes = assert.commandWorked(db.runCommand({
"listCollections": 1,
"nameOnly": true,
"filter": {$or: [{type: 'collection'}, {type: {$exists: false}}]},
"$readPreference": {"mode": "nearest"},
}));
const collectionNames = new DBCommandCursor(db, listCollRes).map(function(z) {
return z.name;
});
for (let collectionName of collectionNames) {
let res = conn.getDB(dbName).getCollection(collectionName).runCommand({
"validate": collectionName,
background: true,
"$readPreference": {"mode": "nearest"}
});
if ((!res.ok && !isIgnorableErrorFunc(res.codeName)) || (res.valid === false)) {
failedValidateResults.push({"ns": dbName + "." + collectionName, "res": res});
}
}
}
// If any commands failed, format and return an error.
if (failedValidateResults.length) {
let errorsArray = [];
for (let nsAndRes of failedValidateResults) {
errorsArray.push({"namespace": nsAndRes.ns, "res": nsAndRes.res});
}
const heading = "Validate command(s) with {background:true} failed against mongod";
print(heading + " '" + conn.host + "': \n" + tojson(errorsArray));
return {ok: 0, error: "Validate failure (search for the following heading): " + heading};
}
return {ok: 1};
};
if (topology.type === Topology.kStandalone) {
let res = validateCollectionsBackgroundThread(topology.mongod);
assert.commandWorked(
res,
() => 'background collection validation against the standalone failed: ' + tojson(res));
} else if (topology.type === Topology.kReplicaSet) {
const threads = [];
try {
for (let replicaMember of topology.nodes) {
const thread =
new Thread(validateCollectionsBackgroundThread, replicaMember, isIgnorableError);
threads.push(thread);
thread.start();
}
} finally {
// Wait for each thread to finish and gather any errors.
let gatheredErrors = [];
const returnData = threads.map(thread => {
try {
thread.join();
// Calling returnData can cause an error thrown in the thread to be thrown again, so
// we do this in a try-catch block.
let res = thread.returnData();
if (!res.ok) {
gatheredErrors.push(res);
}
} catch (e) {
gatheredErrors.push(e);
}
});
if (gatheredErrors.length) {
throw new Error(
"Background collection validation was not successful against all replica set " +
"members: \n" + tojson(gatheredErrors));
}
}
} else {
throw new Error('Unsupported topology configuration: ' + tojson(topology));
}
})();