var MongoRunner, _startMongod, startMongoProgram, runMongoProgram, startMongoProgramNoConnect, myPort; (function() { "use strict"; var shellVersion = version; // Record the exit codes of mongod and mongos processes that crashed during startup keyed by // port. This map is cleared when MongoRunner._startWithArgs and MongoRunner.stopMongod/s are // called. var serverExitCodeMap = {}; var _parsePath = function() { var dbpath = ""; for (var i = 0; i < arguments.length; ++i) if (arguments[i] == "--dbpath") dbpath = arguments[i + 1]; if (dbpath == "") throw Error("No dbpath specified"); return dbpath; }; var _parsePort = function() { var port = ""; for (var i = 0; i < arguments.length; ++i) if (arguments[i] == "--port") port = arguments[i + 1]; if (port == "") throw Error("No port specified"); return port; }; var createMongoArgs = function(binaryName, args) { if (!Array.isArray(args)) { throw new Error("The second argument to createMongoArgs must be an array"); } var fullArgs = [binaryName]; if (args.length == 1 && isObject(args[0])) { var o = args[0]; for (var k in o) { if (o.hasOwnProperty(k)) { if (k == "v" && isNumber(o[k])) { var n = o[k]; if (n > 0) { if (n > 10) n = 10; var temp = "-"; while (n-- > 0) temp += "v"; fullArgs.push(temp); } } else { fullArgs.push("--" + k); if (o[k] != "") fullArgs.push("" + o[k]); } } } } else { for (var i = 0; i < args.length; i++) fullArgs.push(args[i]); } return fullArgs; }; MongoRunner = function() {}; MongoRunner.dataDir = "/data/db"; MongoRunner.dataPath = "/data/db/"; MongoRunner.mongodPath = "mongod"; MongoRunner.mongosPath = "mongos"; MongoRunner.mongoShellPath = "mongo"; MongoRunner.VersionSub = function(pattern, version) { this.pattern = pattern; this.version = version; }; (function() { // Hang Analyzer integration. function getPids() { let pids = []; if (typeof TestData !== 'undefined' && typeof TestData.peerPids !== 'undefined') { pids = pids.concat(TestData.peerPids); } pids = pids.concat(MongoRunner.runningChildPids()); return pids; } // A path.join-like thing for paths that must work // on Windows (\-separated) and *nix (/-separated). function pathJoin(...parts) { const separator = _isWindows() ? '\\' : '/'; return parts.join(separator); } // Internal state to determine if the hang analyzer should be enabled or not. // Accessible via global setter/getter defined below. let _hangAnalyzerEnabled = true; /** * Run `./buildscripts/hang_analyzer.py`. * * @param {Number[]} pids * optional pids of processes to pass to hang_analyzer.py. * If not specified will use `TestData.peerPids` (pids of * "fixture" processes started and passed in by resmoke) * plus `MongoRunner.runningChildPids()` which includes all * child processes started by `MongoRunner.runMongo*()` etc. */ function runHangAnalyzer(pids) { if (typeof TestData === 'undefined') { print("Skipping runHangAnalyzer: no TestData (not running from resmoke)"); return; } if (!_hangAnalyzerEnabled) { print('Skipping runHangAnalyzer: manually disabled'); return; } if (typeof pids === 'undefined') { pids = getPids(); } if (pids.length <= 0) { print("Skipping runHangAnalyzer: no running child or peer mongo processes."); return; } // Result of runningChildPids may be NumberLong(), so // add 0 to convert to Number. pids = pids.map(p => p + 0).join(','); print(`Running hang_analyzer.py for pids [${pids}]`); const scriptPath = pathJoin('.', 'buildscripts', 'hang_analyzer.py'); return runProgram('python', scriptPath, '-c', '-d', pids); } MongoRunner.runHangAnalyzer = runHangAnalyzer; MongoRunner.runHangAnalyzer.enable = function() { _hangAnalyzerEnabled = true; }; MongoRunner.runHangAnalyzer.disable = function() { _hangAnalyzerEnabled = false; }; })(); /** * Returns an array of version elements from a version string. * * "3.3.4-fade3783" -> ["3", "3", "4-fade3783" ] * "3.2" -> [ "3", "2" ] * 3 -> exception: versions must have at least two components. */ var convertVersionStringToArray = function(versionString) { assert("" !== versionString, "Version strings must not be empty"); var versionArray = versionString.split('.'); assert.gt(versionArray.length, 1, "MongoDB versions must have at least two components to compare, but \"" + versionString + "\" has " + versionArray.length); return versionArray; }; /** * Returns the major version string from a version string. * * 3.3.4-fade3783 -> 3.3 * 3.2 -> 3.2 * 3 -> exception: versions must have at least two components. */ var extractMajorVersionFromVersionString = function(versionString) { return convertVersionStringToArray(versionString).slice(0, 2).join('.'); }; // These patterns allow substituting the binary versions used for each version string to support // the // dev/stable MongoDB release cycle. // // If you add a new version substitution to this list, you should add it to the lists of // versions being checked in 'verify_versions_test.js' to verify it is susbstituted correctly. MongoRunner.binVersionSubs = [ new MongoRunner.VersionSub("latest", shellVersion()), new MongoRunner.VersionSub(extractMajorVersionFromVersionString(shellVersion()), shellVersion()), // To-be-updated when we branch for the next release. new MongoRunner.VersionSub("last-stable", "4.2") ]; MongoRunner.getBinVersionFor = function(version) { if (version instanceof MongoRunner.versionIterator.iterator) { version = version.current(); } if (version == null) version = ""; version = version.trim(); if (version === "") version = "latest"; // See if this version is affected by version substitutions for (var i = 0; i < MongoRunner.binVersionSubs.length; i++) { var sub = MongoRunner.binVersionSubs[i]; if (sub.pattern == version) { return sub.version; } } return version; }; /** * Returns true if two version strings could represent the same version. This is true * if, after passing the versions through getBinVersionFor, the versions have the * same value for each version component up through the length of the shorter version. * * That is, 3.2.4 compares equal to 3.2, but 3.2.4 does not compare equal to 3.2.3. */ MongoRunner.areBinVersionsTheSame = function(versionA, versionB) { // Check for invalid version strings first. convertVersionStringToArray(MongoRunner.getBinVersionFor(versionA)); convertVersionStringToArray(MongoRunner.getBinVersionFor(versionB)); try { return (0 === MongoRunner.compareBinVersions(versionA, versionB)); } catch (err) { // compareBinVersions() throws an error if two versions differ only by the git hash. return false; } }; /** * Compares two version strings and returns: * 1, if the first is more recent * 0, if they are equal * -1, if the first is older * * Note that this function only compares up to the length of the shorter version. * Because of this, minor versions will compare equal to the major versions they stem * from, but major-major and minor-minor version pairs will undergo strict comparison. */ MongoRunner.compareBinVersions = function(versionA, versionB) { let stringA = versionA; let stringB = versionB; versionA = convertVersionStringToArray(MongoRunner.getBinVersionFor(versionA)); versionB = convertVersionStringToArray(MongoRunner.getBinVersionFor(versionB)); // Treat the githash as a separate element, if it's present. versionA.push(...versionA.pop().split("-")); versionB.push(...versionB.pop().split("-")); var elementsToCompare = Math.min(versionA.length, versionB.length); for (var i = 0; i < elementsToCompare; ++i) { var elementA = versionA[i]; var elementB = versionB[i]; if (elementA === elementB) { continue; } var numA = parseInt(elementA); var numB = parseInt(elementB); assert(!isNaN(numA) && !isNaN(numB), "Cannot compare non-equal non-numeric versions."); if (numA > numB) { return 1; } else if (numA < numB) { return -1; } assert(false, `Unreachable case. Provided versions: {${stringA}, ${stringB}}`); } return 0; }; MongoRunner.logicalOptions = { runId: true, env: true, pathOpts: true, remember: true, noRemember: true, appendOptions: true, restart: true, noCleanData: true, cleanData: true, startClean: true, forceLock: true, useLogFiles: true, logFile: true, useHostName: true, useHostname: true, noReplSet: true, forgetPort: true, arbiter: true, noJournal: true, binVersion: true, waitForConnect: true, bridgeOptions: true, skipValidation: true, }; MongoRunner.toRealPath = function(path, pathOpts) { // Replace all $pathOptions with actual values pathOpts = pathOpts || {}; path = path.replace(/\$dataPath/g, MongoRunner.dataPath); path = path.replace(/\$dataDir/g, MongoRunner.dataDir); for (var key in pathOpts) { path = path.replace(RegExp("\\$" + RegExp.escape(key), "g"), pathOpts[key]); } // Relative path // Detect Unix and Windows absolute paths // as well as Windows drive letters // Also captures Windows UNC paths if (!path.match(/^(\/|\\|[A-Za-z]:)/)) { if (path != "" && !path.endsWith("/")) path += "/"; path = MongoRunner.dataPath + path; } return path; }; MongoRunner.toRealDir = function(path, pathOpts) { path = MongoRunner.toRealPath(path, pathOpts); if (path.endsWith("/")) path = path.substring(0, path.length - 1); return path; }; MongoRunner.toRealFile = MongoRunner.toRealDir; /** * Returns an iterator object which yields successive versions on calls to advance(), starting * from a random initial position, from an array of versions. * * If passed a single version string or an already-existing version iterator, just returns the * object itself, since it will yield correctly on calls to advance(). * * @param {Array.}|{String}|{versionIterator} */ MongoRunner.versionIterator = function(arr, isRandom) { // If this isn't an array of versions, or is already an iterator, just use it if (typeof arr == "string") return arr; if (arr.isVersionIterator) return arr; if (isRandom == undefined) isRandom = false; // Starting pos var i = isRandom ? parseInt(Random.rand() * arr.length) : 0; return new MongoRunner.versionIterator.iterator(i, arr); }; MongoRunner.versionIterator.iterator = function(i, arr) { if (!Array.isArray(arr)) { throw new Error("Expected an array for the second argument, but got: " + tojson(arr)); } this.current = function current() { return arr[i]; }; // We define the toString() method as an alias for current() so that concatenating a version // iterator with a string returns the next version in the list without introducing any // side-effects. this.toString = this.current; this.advance = function advance() { i = (i + 1) % arr.length; }; this.isVersionIterator = true; }; /** * Converts the args object by pairing all keys with their value and appending * dash-dash (--) to the keys. The only exception to this rule are keys that * are defined in MongoRunner.logicalOptions, of which they will be ignored. * * @param {string} binaryName * @param {Object} args * * @return {Array.} an array of parameter strings that can be passed * to the binary. */ MongoRunner.arrOptions = function(binaryName, args) { var fullArgs = [""]; // isObject returns true even if "args" is an array, so the else branch of this statement is // dead code. See SERVER-14220. if (isObject(args) || (args.length == 1 && isObject(args[0]))) { var o = isObject(args) ? args : args[0]; // If we've specified a particular binary version, use that if (o.binVersion && o.binVersion != "" && o.binVersion != shellVersion()) { binaryName += "-" + o.binVersion; } // Manage legacy options var isValidOptionForBinary = function(option, value) { if (!o.binVersion) return true; return true; }; var addOptionsToFullArgs = function(k, v) { if (v === undefined || v === null) return; fullArgs.push("--" + k); if (v != "") { fullArgs.push("" + v); } }; for (var k in o) { // Make sure our logical option should be added to the array of options if (!o.hasOwnProperty(k) || k in MongoRunner.logicalOptions || !isValidOptionForBinary(k, o[k])) continue; if ((k == "v" || k == "verbose") && isNumber(o[k])) { var n = o[k]; if (n > 0) { if (n > 10) n = 10; var temp = "-"; while (n-- > 0) temp += "v"; fullArgs.push(temp); } } else if (k === "setParameter" && isObject(o[k])) { // If the value associated with the setParameter option is an object, we want // to add all key-value pairs in that object as separate --setParameters. Object.keys(o[k]).forEach(function(paramKey) { addOptionsToFullArgs(k, "" + paramKey + "=" + o[k][paramKey]); }); } else { addOptionsToFullArgs(k, o[k]); } } } else { for (var i = 0; i < args.length; i++) fullArgs.push(args[i]); } fullArgs[0] = binaryName; return fullArgs; }; MongoRunner.arrToOpts = function(arr) { var opts = {}; for (var i = 1; i < arr.length; i++) { if (arr[i].startsWith("-")) { var opt = arr[i].replace(/^-/, "").replace(/^-/, ""); if (arr.length > i + 1 && !arr[i + 1].startsWith("-")) { opts[opt] = arr[i + 1]; i++; } else { opts[opt] = ""; } if (opt.replace(/v/g, "") == "") { opts["verbose"] = opt.length; } } } return opts; }; MongoRunner.savedOptions = {}; MongoRunner.mongoOptions = function(opts) { // Don't remember waitForConnect var waitForConnect = opts.waitForConnect; delete opts.waitForConnect; // If we're a mongo object if (opts.getDB) { opts = {restart: opts.runId}; } // Initialize and create a copy of the opts opts = Object.merge(opts || {}, {}); if (!opts.restart) opts.restart = false; // RunId can come from a number of places // If restart is passed as an old connection if (opts.restart && opts.restart.getDB) { opts.runId = opts.restart.runId; opts.restart = true; } // If it's the runId itself else if (isObject(opts.restart)) { opts.runId = opts.restart; opts.restart = true; } if (isObject(opts.remember)) { opts.runId = opts.remember; opts.remember = true; } else if (opts.remember == undefined) { // Remember by default if we're restarting opts.remember = opts.restart; } // If we passed in restart : or runId : if (isObject(opts.runId) && opts.runId.runId) opts.runId = opts.runId.runId; if (opts.restart && opts.remember) { opts = Object.merge(MongoRunner.savedOptions[opts.runId], opts); } // Create a new runId opts.runId = opts.runId || ObjectId(); if (opts.forgetPort) { delete opts.port; } // Normalize and get the binary version to use if (opts.hasOwnProperty('binVersion')) { if (opts.binVersion instanceof MongoRunner.versionIterator.iterator) { // Advance the version iterator so that subsequent calls to // MongoRunner.mongoOptions() use the next version in the list. const iterator = opts.binVersion; opts.binVersion = iterator.current(); iterator.advance(); } opts.binVersion = MongoRunner.getBinVersionFor(opts.binVersion); } // Default for waitForConnect is true opts.waitForConnect = (waitForConnect == undefined || waitForConnect == null) ? true : waitForConnect; opts.port = opts.port || allocatePort(); opts.pathOpts = Object.merge(opts.pathOpts || {}, {port: "" + opts.port, runId: "" + opts.runId}); var shouldRemember = (!opts.restart && !opts.noRemember) || (opts.restart && opts.appendOptions); if (shouldRemember) { MongoRunner.savedOptions[opts.runId] = Object.merge(opts, {}); // We don't want to persist 'waitForConnect' across node restarts. delete MongoRunner.savedOptions[opts.runId].waitForConnect; } if (jsTestOptions().networkMessageCompressors) { opts.networkMessageCompressors = jsTestOptions().networkMessageCompressors; } if (!opts.hasOwnProperty('bind_ip')) { opts.bind_ip = "0.0.0.0"; } return opts; }; // Returns an array of integers representing the version provided. // Ex: "3.3.12" => [3, 3, 12] var _convertVersionToIntegerArray = function(version) { var versionParts = convertVersionStringToArray(version).slice(0, 3).map(part => parseInt(part, 10)); if (versionParts.length === 2) { versionParts.push(Infinity); } return versionParts; }; // Returns if version2 is equal to, or came after, version 1. var _isMongodVersionEqualOrAfter = function(version1, version2) { if (version2 === "latest") { return true; } var versionParts1 = _convertVersionToIntegerArray(version1); var versionParts2 = _convertVersionToIntegerArray(version2); if (versionParts2[0] > versionParts1[0] || (versionParts2[0] === versionParts1[0] && versionParts2[1] > versionParts1[1]) || (versionParts2[0] === versionParts1[0] && versionParts2[1] === versionParts1[1] && versionParts2[2] >= versionParts1[2])) { return true; } return false; }; // Removes a setParameter parameter from mongods running a version that won't recognize them. var _removeSetParameterIfBeforeVersion = function(opts, parameterName, requiredVersion) { var versionCompatible = (opts.binVersion === "" || opts.binVersion === undefined || _isMongodVersionEqualOrAfter(requiredVersion, opts.binVersion)); if (!versionCompatible && opts.setParameter && opts.setParameter[parameterName] != undefined) { print("Removing '" + parameterName + "' setParameter with value " + opts.setParameter[parameterName] + " because it isn't compatibile with mongod running version " + opts.binVersion); delete opts.setParameter[parameterName]; } }; /** * @option {object} opts * * { * dbpath {string} * useLogFiles {boolean}: use with logFile option. * logFile {string}: path to the log file. If not specified and useLogFiles * is true, automatically creates a log file inside dbpath. * noJournal {boolean} * keyFile * replSet * oplogSize * } */ MongoRunner.mongodOptions = function(opts = {}) { opts = MongoRunner.mongoOptions(opts); if (jsTestOptions().alwaysUseLogFiles) { if (opts.cleanData || opts.startClean || opts.noCleanData === false || opts.useLogFiles === false) { throw new Error("Always using log files, but received conflicting option."); } opts.cleanData = false; opts.startClean = false; opts.noCleanData = true; opts.useLogFiles = true; opts.logappend = ""; } opts.dbpath = MongoRunner.toRealDir(opts.dbpath || "$dataDir/mongod-$port", opts.pathOpts); opts.pathOpts = Object.merge(opts.pathOpts, {dbpath: opts.dbpath}); _removeSetParameterIfBeforeVersion(opts, "writePeriodicNoops", "3.3.12"); _removeSetParameterIfBeforeVersion(opts, "numInitialSyncAttempts", "3.3.12"); _removeSetParameterIfBeforeVersion(opts, "numInitialSyncConnectAttempts", "3.3.12"); _removeSetParameterIfBeforeVersion(opts, "migrationLockAcquisitionMaxWaitMS", "4.1.7"); if (!opts.logFile && opts.useLogFiles) { opts.logFile = opts.dbpath + "/mongod.log"; } else if (opts.logFile) { opts.logFile = MongoRunner.toRealFile(opts.logFile, opts.pathOpts); } if (opts.logFile !== undefined) { opts.logpath = opts.logFile; } if ((jsTestOptions().noJournal || opts.noJournal) && !('journal' in opts) && !('configsvr' in opts)) { opts.nojournal = ""; } if (jsTestOptions().keyFile && !opts.keyFile) { opts.keyFile = jsTestOptions().keyFile; } if (opts.hasOwnProperty("enableEncryption")) { // opts.enableEncryption, if set, must be an empty string if (opts.enableEncryption !== "") { throw new Error("The enableEncryption option must be an empty string if it is " + "specified"); } } else if (jsTestOptions().enableEncryption !== undefined) { if (jsTestOptions().enableEncryption !== "") { throw new Error("The enableEncryption option must be an empty string if it is " + "specified"); } opts.enableEncryption = ""; } if (opts.hasOwnProperty("encryptionKeyFile")) { // opts.encryptionKeyFile, if set, must be a string if (typeof opts.encryptionKeyFile !== "string") { throw new Error("The encryptionKeyFile option must be a string if it is specified"); } } else if (jsTestOptions().encryptionKeyFile !== undefined) { if (typeof (jsTestOptions().encryptionKeyFile) !== "string") { throw new Error("The encryptionKeyFile option must be a string if it is specified"); } opts.encryptionKeyFile = jsTestOptions().encryptionKeyFile; } if (opts.hasOwnProperty("auditDestination")) { // opts.auditDestination, if set, must be a string if (typeof opts.auditDestination !== "string") { throw new Error("The auditDestination option must be a string if it is specified"); } } else if (jsTestOptions().auditDestination !== undefined) { if (typeof (jsTestOptions().auditDestination) !== "string") { throw new Error("The auditDestination option must be a string if it is specified"); } opts.auditDestination = jsTestOptions().auditDestination; } if (opts.noReplSet) opts.replSet = null; if (opts.arbiter) opts.oplogSize = 1; return opts; }; MongoRunner.mongosOptions = function(opts) { opts = MongoRunner.mongoOptions(opts); // Normalize configdb option to be host string if currently a host if (opts.configdb && opts.configdb.getDB) { opts.configdb = opts.configdb.host; } if (jsTestOptions().alwaysUseLogFiles) { if (opts.useLogFiles === false) { throw new Error("Always using log files, but received conflicting option."); } opts.useLogFiles = true; opts.logappend = ""; } opts.pathOpts = Object.merge(opts.pathOpts, {configdb: opts.configdb.replace(/:|\/|,/g, "-")}); if (!opts.logFile && opts.useLogFiles) { opts.logFile = MongoRunner.toRealFile("$dataDir/mongos-$configdb-$port.log", opts.pathOpts); } else if (opts.logFile) { opts.logFile = MongoRunner.toRealFile(opts.logFile, opts.pathOpts); } if (opts.logFile !== undefined) { opts.logpath = opts.logFile; } var testOptions = jsTestOptions(); if (testOptions.keyFile && !opts.keyFile) { opts.keyFile = testOptions.keyFile; } if (opts.hasOwnProperty("auditDestination")) { // opts.auditDestination, if set, must be a string if (typeof opts.auditDestination !== "string") { throw new Error("The auditDestination option must be a string if it is specified"); } } else if (testOptions.auditDestination !== undefined) { if (typeof (testOptions.auditDestination) !== "string") { throw new Error("The auditDestination option must be a string if it is specified"); } opts.auditDestination = testOptions.auditDestination; } if (!opts.hasOwnProperty('binVersion') && testOptions.mongosBinVersion) { opts.binVersion = MongoRunner.getBinVersionFor(testOptions.mongosBinVersion); } // If the mongos is being restarted with a newer version, make sure we remove any options // that no longer exist in the newer version. if (opts.restart && MongoRunner.areBinVersionsTheSame('latest', opts.binVersion)) { delete opts.noAutoSplit; } return opts; }; /** * @return {NumberLong[]} Running pids e.g. those started by `MongoRunner.runMongod`. */ MongoRunner.runningChildPids = function() { return _runningMongoChildProcessIds(); }; /** * Starts a mongod instance. * * @param {Object} opts * * { * useHostName {boolean}: Uses hostname of machine if true. * forceLock {boolean}: Deletes the lock file if set to true. * dbpath {string}: location of db files. * cleanData {boolean}: Removes all files in dbpath if true. * startClean {boolean}: same as cleanData. * noCleanData {boolean}: Do not clean files (cleanData takes priority). * binVersion {string}: version for binary (also see MongoRunner.binVersionSubs). * * @see MongoRunner.mongodOptions for other options * } * * @return {Mongo} connection object to the started mongod instance. * * @see MongoRunner.arrOptions */ MongoRunner.runMongod = function(opts) { opts = opts || {}; var env = undefined; var useHostName = true; var runId = null; var waitForConnect = true; var fullOptions = opts; if (isObject(opts)) { opts = MongoRunner.mongodOptions(opts); fullOptions = opts; if (opts.useHostName != undefined) { useHostName = opts.useHostName; } else if (opts.useHostname != undefined) { useHostName = opts.useHostname; } else { useHostName = true; // Default to true } env = opts.env; runId = opts.runId; waitForConnect = opts.waitForConnect; if (opts.forceLock) removeFile(opts.dbpath + "/mongod.lock"); if ((opts.cleanData || opts.startClean) || (!opts.restart && !opts.noCleanData)) { print("Resetting db path '" + opts.dbpath + "'"); resetDbpath(opts.dbpath); } var mongodProgram = MongoRunner.mongodPath; opts = MongoRunner.arrOptions(mongodProgram, opts); } var mongod = MongoRunner._startWithArgs(opts, env, waitForConnect); if (!mongod) { return null; } mongod.commandLine = MongoRunner.arrToOpts(opts); mongod.name = (useHostName ? getHostName() : "localhost") + ":" + mongod.commandLine.port; mongod.host = mongod.name; mongod.port = parseInt(mongod.commandLine.port); mongod.runId = runId || ObjectId(); mongod.dbpath = fullOptions.dbpath; mongod.savedOptions = MongoRunner.savedOptions[mongod.runId]; mongod.fullOptions = fullOptions; return mongod; }; MongoRunner.runMongos = function(opts) { opts = opts || {}; var env = undefined; var useHostName = false; var runId = null; var waitForConnect = true; var fullOptions = opts; if (isObject(opts)) { opts = MongoRunner.mongosOptions(opts); fullOptions = opts; useHostName = opts.useHostName || opts.useHostname; runId = opts.runId; waitForConnect = opts.waitForConnect; env = opts.env; var mongosProgram = MongoRunner.mongosPath; opts = MongoRunner.arrOptions(mongosProgram, opts); } var mongos = MongoRunner._startWithArgs(opts, env, waitForConnect); if (!mongos) { return null; } mongos.commandLine = MongoRunner.arrToOpts(opts); mongos.name = (useHostName ? getHostName() : "localhost") + ":" + mongos.commandLine.port; mongos.host = mongos.name; mongos.port = parseInt(mongos.commandLine.port); mongos.runId = runId || ObjectId(); mongos.savedOptions = MongoRunner.savedOptions[mongos.runId]; mongos.fullOptions = fullOptions; return mongos; }; MongoRunner.StopError = function(returnCode) { this.name = "StopError"; this.returnCode = returnCode; this.message = "MongoDB process stopped with exit code: " + this.returnCode; this.stack = this.toString() + "\n" + (new Error()).stack; }; MongoRunner.StopError.prototype = Object.create(Error.prototype); MongoRunner.StopError.prototype.constructor = MongoRunner.StopError; // Constants for exit codes of MongoDB processes MongoRunner.EXIT_ABORT = -6; MongoRunner.EXIT_CLEAN = 0; MongoRunner.EXIT_BADOPTIONS = 2; MongoRunner.EXIT_REPLICATION_ERROR = 3; MongoRunner.EXIT_NEED_UPGRADE = 4; MongoRunner.EXIT_SHARDING_ERROR = 5; // SIGKILL is translated to TerminateProcess() on Windows, which causes the program to // terminate with exit code 1. MongoRunner.EXIT_SIGKILL = _isWindows() ? 1 : -9; MongoRunner.EXIT_KILL = 12; MongoRunner.EXIT_ABRUPT = 14; MongoRunner.EXIT_NTSERVICE_ERROR = 20; MongoRunner.EXIT_JAVA = 21; MongoRunner.EXIT_OOM_MALLOC = 42; MongoRunner.EXIT_OOM_REALLOC = 43; MongoRunner.EXIT_FS = 45; MongoRunner.EXIT_CLOCK_SKEW = 47; // OpTime clock skew; deprecated MongoRunner.EXIT_NET_ERROR = 48; MongoRunner.EXIT_WINDOWS_SERVICE_STOP = 49; MongoRunner.EXIT_POSSIBLE_CORRUPTION = 60; MongoRunner.EXIT_NEED_DOWNGRADE = 62; MongoRunner.EXIT_UNCAUGHT = 100; // top level exception that wasn't caught MongoRunner.EXIT_TEST = 101; MongoRunner.validateCollectionsCallback = function(port) {}; /** * Kills a mongod process. * * @param {Mongo} conn the connection object to the process to kill * @param {number} signal The signal number to use for killing * @param {Object} opts Additional options. Format: * { * auth: { * user {string}: admin user name * pwd {string}: admin password * }, * skipValidation: , * allowedExitCode: * } * @param {boolean} waitpid should we wait for the process to terminate after stopping it. * * Note: The auth option is required in a authenticated mongod running in Windows since * it uses the shutdown command, which requires admin credentials. */ MongoRunner.stopMongod = function(conn, signal, opts, waitpid) { if (!conn.pid) { throw new Error("first arg must have a `pid` property; " + "it is usually the object returned from MongoRunner.runMongod/s"); } if (!conn.port) { throw new Error("first arg must have a `port` property; " + "it is usually the object returned from MongoRunner.runMongod/s"); } signal = parseInt(signal) || 15; opts = opts || {}; waitpid = (waitpid === undefined) ? true : waitpid; var allowedExitCode = MongoRunner.EXIT_CLEAN; if (opts.allowedExitCode) { allowedExitCode = opts.allowedExitCode; } var port = parseInt(conn.port); var pid = conn.pid; // If the return code is in the serverExitCodeMap, it means the server crashed on startup. // We just use the recorded return code instead of stopping the program. var returnCode; if (serverExitCodeMap.hasOwnProperty(port)) { returnCode = serverExitCodeMap[port]; delete serverExitCodeMap[port]; } else { // Invoke callback to validate collections and indexes before shutting down mongod. // We skip calling the callback function when the expected return code of // the mongod process is non-zero since it's likely the process has already exited. var skipValidation = false; if (opts.skipValidation) { skipValidation = true; } if (allowedExitCode === MongoRunner.EXIT_CLEAN && !skipValidation) { MongoRunner.validateCollectionsCallback(port); } returnCode = _stopMongoProgram(port, signal, opts, waitpid); } // If we are not waiting for shutdown, then there is no exit code to check. if (!waitpid) { return 0; } if (allowedExitCode !== returnCode) { throw new MongoRunner.StopError(returnCode); } else if (returnCode !== MongoRunner.EXIT_CLEAN) { print("MongoDB process on port " + port + " intentionally exited with error code ", returnCode); } return returnCode; }; MongoRunner.stopMongos = MongoRunner.stopMongod; // Given a test name figures out a directory for that test to use for dump files and makes sure // that directory exists and is empty. MongoRunner.getAndPrepareDumpDirectory = function(testName) { var dir = MongoRunner.dataPath + testName + "_external/"; resetDbpath(dir); return dir; }; // Start a mongod instance and return a 'Mongo' object connected to it. // This function's arguments are passed as command line arguments to mongod. // The specified 'dbpath' is cleared if it exists, created if not. // var conn = _startMongodEmpty("--port", 30000, "--dbpath", "asdf"); var _startMongodEmpty = function() { var args = createMongoArgs("mongod", Array.from(arguments)); var dbpath = _parsePath.apply(null, args); resetDbpath(dbpath); return startMongoProgram.apply(null, args); }; _startMongod = function() { print("startMongod WARNING DELETES DATA DIRECTORY THIS IS FOR TESTING ONLY"); return _startMongodEmpty.apply(null, arguments); }; /** * Returns a new argArray with any test-specific arguments added. */ function appendSetParameterArgs(argArray) { function argArrayContains(key) { return (argArray .filter((val) => { return typeof val === "string" && val.indexOf(key) === 0; }) .length > 0); } function argArrayContainsSetParameterValue(value) { assert(value.endsWith("="), "Expected value argument to be of the form ="); return argArray.some(function(el) { return typeof el === "string" && el.startsWith(value); }); } // programName includes the version, e.g., mongod-3.2. // baseProgramName is the program name without any version information, e.g., mongod. let programName = argArray[0]; let [baseProgramName, programVersion] = programName.split("-"); let programMajorMinorVersion = 0; if (programVersion) { let [major, minor, point] = programVersion.split("."); programMajorMinorVersion = parseInt(major) * 100 + parseInt(minor); } if (baseProgramName === 'mongod' || baseProgramName === 'mongos') { if (jsTest.options().enableTestCommands) { argArray.push(...['--setParameter', "enableTestCommands=1"]); } if (jsTest.options().authMechanism && jsTest.options().authMechanism != "SCRAM-SHA-1") { if (!argArrayContainsSetParameterValue('authenticationMechanisms=')) { argArray.push(...['--setParameter', "authenticationMechanisms=" + jsTest.options().authMechanism]); } } if (jsTest.options().auth) { argArray.push(...['--setParameter', "enableLocalhostAuthBypass=false"]); } // New options in 3.5.x if (!programMajorMinorVersion || programMajorMinorVersion >= 305) { if (jsTest.options().serviceExecutor) { if (!argArrayContains("--serviceExecutor")) { argArray.push(...["--serviceExecutor", jsTest.options().serviceExecutor]); } } if (jsTest.options().transportLayer) { if (!argArrayContains("--transportLayer")) { argArray.push(...["--transportLayer", jsTest.options().transportLayer]); } } // Disable background cache refreshing to avoid races in tests argArray.push(...['--setParameter', "disableLogicalSessionCacheRefresh=true"]); } // New options in 4.3.x if (!programMajorMinorVersion || programMajorMinorVersion >= 403) { if (jsTest.options().logFormat) { if (!argArrayContains("--logFormat")) { argArray.push(...["--logFormat", jsTest.options().logFormat]); } } } // Since options may not be backward compatible, mongos options are not // set on older versions, e.g., mongos-3.0. if (programName.endsWith('mongos')) { // apply setParameters for mongos if (jsTest.options().setParametersMongos) { let params = jsTest.options().setParametersMongos; for (let paramName of Object.keys(params)) { // Only set the 'logComponentVerbosity' parameter if it has not already // been specified in the given argument array. This means that any // 'logComponentVerbosity' settings passed through via TestData will // always be overridden by settings passed directly to MongoRunner from // within the shell. if (paramName === "logComponentVerbosity" && argArrayContains("logComponentVerbosity")) { continue; } const paramVal = ((param) => { if (typeof param === "object") { return JSON.stringify(param); } return param; })(params[paramName]); const setParamStr = paramName + "=" + paramVal; argArray.push(...['--setParameter', setParamStr]); } } } else if (baseProgramName === 'mongod') { if (jsTestOptions().roleGraphInvalidationIsFatal) { argArray.push(...['--setParameter', "roleGraphInvalidationIsFatal=true"]); } // Set storageEngine for mongod. There was no storageEngine parameter before 3.0. if (jsTest.options().storageEngine && (!programVersion || programMajorMinorVersion >= 300)) { if (!argArrayContains("--storageEngine")) { argArray.push(...['--storageEngine', jsTest.options().storageEngine]); } } // New mongod-specific options in 4.0.x if (!programMajorMinorVersion || programMajorMinorVersion >= 400) { if (jsTest.options().transactionLifetimeLimitSeconds !== undefined) { if (!argArrayContainsSetParameterValue("transactionLifetimeLimitSeconds=")) { argArray.push(...["--setParameter", "transactionLifetimeLimitSeconds=" + jsTest.options().transactionLifetimeLimitSeconds]); } } } // TODO: Make this unconditional in 3.8. if (!programMajorMinorVersion || programMajorMinorVersion > 304) { if (!argArrayContainsSetParameterValue('orphanCleanupDelaySecs=')) { argArray.push(...['--setParameter', 'orphanCleanupDelaySecs=1']); } } if (!programMajorMinorVersion || programMajorMinorVersion >= 306) { if (jsTest.options().storageEngine === "wiredTiger" || !jsTest.options().storageEngine) { if (jsTest.options().enableMajorityReadConcern !== undefined && !argArrayContains("--enableMajorityReadConcern")) { argArray.push(...['--enableMajorityReadConcern', jsTest.options().enableMajorityReadConcern.toString()]); } } } // Since options may not be backward compatible, mongod options are not // set on older versions, e.g., mongod-3.0. if (programName.endsWith('mongod')) { if (jsTest.options().storageEngine === "wiredTiger" || !jsTest.options().storageEngine) { if (jsTest.options().storageEngineCacheSizeGB && !argArrayContains('--wiredTigerCacheSizeGB')) { argArray.push(...['--wiredTigerCacheSizeGB', jsTest.options().storageEngineCacheSizeGB]); } if (jsTest.options().wiredTigerEngineConfigString && !argArrayContains('--wiredTigerEngineConfigString')) { argArray.push(...['--wiredTigerEngineConfigString', jsTest.options().wiredTigerEngineConfigString]); } if (jsTest.options().wiredTigerCollectionConfigString && !argArrayContains('--wiredTigerCollectionConfigString')) { argArray.push(...['--wiredTigerCollectionConfigString', jsTest.options().wiredTigerCollectionConfigString]); } if (jsTest.options().wiredTigerIndexConfigString && !argArrayContains('--wiredTigerIndexConfigString')) { argArray.push(...['--wiredTigerIndexConfigString', jsTest.options().wiredTigerIndexConfigString]); } } else if (jsTest.options().storageEngine === "rocksdb") { if (jsTest.options().storageEngineCacheSizeGB) { argArray.push( ...['--rocksdbCacheSizeGB', jsTest.options().storageEngineCacheSizeGB]); } } else if (jsTest.options().storageEngine === "inMemory") { if (jsTest.options().storageEngineCacheSizeGB && !argArrayContains("--inMemorySizeGB")) { argArray.push( ...["--inMemorySizeGB", jsTest.options().storageEngineCacheSizeGB]); } } // apply setParameters for mongod. The 'setParameters' field should be given as // a plain JavaScript object, where each key is a parameter name and the value // is the value to set for that parameter. if (jsTest.options().setParameters) { let params = jsTest.options().setParameters; for (let paramName of Object.keys(params)) { // Only set the 'logComponentVerbosity' parameter if it has not already // been specified in the given argument array. This means that any // 'logComponentVerbosity' settings passed through via TestData will // always be overridden by settings passed directly to MongoRunner from // within the shell. if (paramName === "logComponentVerbosity" && argArrayContains("logComponentVerbosity")) { continue; } const paramVal = ((param) => { if (typeof param === "object") { return JSON.stringify(param); } return param; })(params[paramName]); const setParamStr = paramName + "=" + paramVal; argArray.push(...['--setParameter', setParamStr]); } } } } } return argArray; } /** * Continuously tries to establish a connection to the server on the specified port. * * If a connection cannot be established within a time limit, an exception will be thrown. If the * process for the given 'pid' is found to no longer be running, this function will terminate and * return null. * * @param {int} [pid] the process id of the node to connect to. * @param {int} [port] the port of the node to connect to. * @returns a new Mongo connection object, or null if the process is not running. */ MongoRunner.awaitConnection = function(pid, port) { var conn = null; assert.soon(function() { try { conn = new Mongo("127.0.0.1:" + port); conn.pid = pid; return true; } catch (e) { var res = checkProgram(pid); if (!res.alive) { print("mongo program was not running at " + port + ", process ended with exit code: " + res.exitCode); serverExitCodeMap[port] = res.exitCode; return true; } } return false; }, "unable to connect to mongo program on port " + port, 600 * 1000); return conn; }; /** * Start a mongo process with a particular argument array. * If we aren't waiting for connect, return {pid: }. * If we are waiting for connect: * returns connection to process on success; * otherwise returns null if we fail to connect. */ MongoRunner._startWithArgs = function(argArray, env, waitForConnect) { // TODO: Make there only be one codepath for starting mongo processes argArray = appendSetParameterArgs(argArray); var port = _parsePort.apply(null, argArray); var pid = -1; if (env === undefined) { pid = _startMongoProgram.apply(null, argArray); } else { pid = _startMongoProgram({args: argArray, env: env}); } delete serverExitCodeMap[port]; if (!waitForConnect) { print("Skip waiting to connect to node with pid=" + pid + ", port=" + port); return { pid: pid, port: port, }; } return MongoRunner.awaitConnection(pid, port); }; /** * DEPRECATED * * Start mongod or mongos and return a Mongo() object connected to there. * This function's first argument is "mongod" or "mongos" program name, \ * and subsequent arguments to this function are passed as * command line arguments to the program. */ startMongoProgram = function() { var port = _parsePort.apply(null, arguments); // Enable test commands. // TODO: Make this work better with multi-version testing so that we can support // enabling this on 2.4 when testing 2.6 var args = Array.from(arguments); args = appendSetParameterArgs(args); var pid = _startMongoProgram.apply(null, args); var m; assert.soon(function() { try { m = new Mongo("127.0.0.1:" + port); m.pid = pid; return true; } catch (e) { var res = checkProgram(pid); if (!res.alive) { print("Could not start mongo program at " + port + ", process ended with exit code: " + res.exitCode); // Break out m = null; return true; } } return false; }, "unable to connect to mongo program on port " + port, 600 * 1000); return m; }; runMongoProgram = function() { var args = Array.from(arguments); args = appendSetParameterArgs(args); var progName = args[0]; // The bsondump tool doesn't support these auth related command line flags. if (jsTestOptions().auth && progName != 'mongod') { args = args.slice(1); args.unshift(progName, '-u', jsTestOptions().authUser, '-p', jsTestOptions().authPassword, '--authenticationDatabase=admin'); } if (progName == 'mongo' && !_useWriteCommandsDefault()) { progName = args[0]; args = args.slice(1); args.unshift(progName, '--useLegacyWriteOps'); } return _runMongoProgram.apply(null, args); }; // Start a mongo program instance. This function's first argument is the // program name, and subsequent arguments to this function are passed as // command line arguments to the program. Returns pid of the spawned program. startMongoProgramNoConnect = function() { var args = Array.from(arguments); args = appendSetParameterArgs(args); var progName = args[0]; if (jsTestOptions().auth) { args = args.slice(1); args.unshift(progName, '-u', jsTestOptions().authUser, '-p', jsTestOptions().authPassword, '--authenticationDatabase=admin'); } if (progName == 'mongo' && !_useWriteCommandsDefault()) { args = args.slice(1); args.unshift(progName, '--useLegacyWriteOps'); } return _startMongoProgram.apply(null, args); }; myPort = function() { var m = db.getMongo(); if (m.host.match(/:/)) return m.host.match(/:(.*)/)[1]; else return 27017; }; }());