1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
// Wrapper around the validate command that can be used to validate index key counts.
'use strict';
function CollectionValidator() {
load('jstests/libs/parallelTester.js');
if (!(this instanceof CollectionValidator)) {
throw new Error('Please use "new CollectionValidator()"');
}
this.validateCollections = function(db, obj) {
function dumpCollection(coll, limit) {
print('Printing indexes in: ' + coll.getFullName());
printjson(coll.getIndexes());
print('Printing the first ' + limit + ' documents in: ' + coll.getFullName());
const res = coll.find().limit(limit);
while (res.hasNext()) {
printjson(res.next());
}
}
assert.eq(typeof db, 'object', 'Invalid `db` object, is the shell connected to a mongod?');
assert.eq(typeof obj, 'object', 'The `obj` argument must be an object');
assert(obj.hasOwnProperty('full'), 'Please specify whether to use full validation');
const full = obj.full;
// Failed collection validation results are saved in failed_res.
let full_res = {ok: 1, failed_res: []};
// Don't run validate on view namespaces.
let filter = {type: "collection"};
if (jsTest.options().skipValidationOnInvalidViewDefinitions) {
// If skipValidationOnInvalidViewDefinitions=true, then we avoid resolving the view
// catalog on the admin database.
//
// TODO SERVER-25493: Remove the $exists clause once performing an initial sync from
// versions of MongoDB <= 3.2 is no longer supported.
filter = {$or: [filter, {type: {$exists: false}}]};
}
// Optionally skip collections.
if (Array.isArray(jsTest.options().skipValidationNamespaces) &&
jsTest.options().skipValidationNamespaces.length > 0) {
let skippedCollections = [];
for (let ns of jsTest.options().skipValidationNamespaces) {
// Strip off the database name from 'ns' to extract the collName.
const collName = ns.replace(new RegExp('^' + db.getName() + '\.'), '');
// Skip the collection 'collName' if the db name was removed from 'ns'.
if (collName !== ns) {
skippedCollections.push({name: {$ne: collName}});
}
}
filter = {$and: [filter, ...skippedCollections]};
}
let collInfo = db.getCollectionInfos(filter);
for (let collDocument of collInfo) {
const coll = db.getCollection(collDocument["name"]);
const res = coll.validate(full);
if (!res.ok || !res.valid) {
if (jsTest.options().skipValidationOnNamespaceNotFound &&
res.errmsg === 'ns not found') {
// During a 'stopStart' backup/restore on the secondary node, the actual list of
// collections can be out of date if ops are still being applied from the oplog.
// In this case we skip the collection if the ns was not found at time of
// validation and continue to next.
print('Skipping collection validation for ' + coll.getFullName() +
' since collection was not found');
continue;
}
const host = db.getMongo().host;
print('Collection validation failed on host ' + host + ' with response: ' +
tojson(res));
dumpCollection(coll, 100);
full_res.failed_res.push(res);
full_res.ok = 0;
}
}
return full_res;
};
// Run a separate thread to validate collections on each server in parallel.
const validateCollectionsThread = function(validatorFunc, host, testData) {
TestData = testData; // Pass the TestData object from main thread.
try {
print('Running validate() on ' + host);
const conn = new Mongo(host);
conn.setSlaveOk();
jsTest.authenticate(conn);
const dbNames = conn.getDBNames();
for (let dbName of dbNames) {
if (!validatorFunc(conn.getDB(dbName), {full: true})) {
return {ok: 0, host: host};
}
}
return {ok: 1};
} catch (e) {
print('Exception caught in scoped thread running validationCollections on server: ' +
host);
return {ok: 0, error: e.toString(), stack: e.stack, host: host};
}
};
this.validateNodes = function(hostList) {
// We run the scoped threads in a try/finally block in case any thread throws an exception,
// in which case we want to still join all the threads.
let threads = [];
try {
hostList.forEach(host => {
const thread = new ScopedThread(
validateCollectionsThread, this.validateCollections, host, TestData);
threads.push(thread);
thread.start();
});
} finally {
// Wait for each thread to finish. Throw an error if any thread fails.
const returnData = threads.map(thread => {
thread.join();
return thread.returnData();
});
returnData.forEach(res => {
assert.commandWorked(res, 'Collection validation failed');
});
}
};
}
// Ensure compatability with existing callers. Cannot use `const` or `let` here since this file may
// be loaded more than once.
var validateCollections = new CollectionValidator().validateCollections;
|