diff options
Diffstat (limited to 'src/mongo/shell/shardingtest.js')
| -rw-r--r-- | src/mongo/shell/shardingtest.js | 994 |
1 files changed, 491 insertions, 503 deletions
diff --git a/src/mongo/shell/shardingtest.js b/src/mongo/shell/shardingtest.js index 1319746ea1a..60f6e83fac7 100644 --- a/src/mongo/shell/shardingtest.js +++ b/src/mongo/shell/shardingtest.js @@ -10,7 +10,6 @@ * * { * name {string}: name for this test - * shouldFailInit {boolean}: if set, assert that this will fail initialization * verbose {number}: the verbosity for the mongos * chunkSize {number}: the chunk size to use as configuration for the cluster * @@ -114,9 +113,6 @@ var ShardingTest = function(params) { // concern (5 minutes) var kDefaultWTimeoutMs = 5 * 60 * 1000; - // Oplog collection name - const kOplogName = 'oplog.rs'; - // Ensure we don't mutate the passed-in parameters. params = Object.extend({}, params, true); @@ -408,24 +404,6 @@ var ShardingTest = function(params) { (timeMillis / 1000) + " seconds ***"); }; - this.stopOnFail = function() { - try { - this.stopAllMongos(); - } catch (e) { - print("Did not successfully stop all mongos."); - } - try { - this.stopAllShards(); - } catch (e) { - print("Did not successfully stop all shards."); - } - try { - this.stopAllConfigServers(); - } catch (e) { - print("Did not successfully stop all config servers."); - } - }; - this.adminCommand = function(cmd) { var res = this.admin.runCommand(cmd); if (res && res.ok == 1) @@ -597,12 +575,53 @@ var ShardingTest = function(params) { }; /** - * Waits up to the specified timeout (with a default of 60s) for the collection to be - * considered well balanced. - **/ - this.awaitBalance = function(collName, dbName, timeToWait, interval) { - const coll = this.s.getCollection(dbName + "." + collName); - this.awaitCollectionBalance(coll, timeToWait, interval); + * Waits up to the specified timeout (with a default of 60s) for the balancer to execute one + * round. If no round has been executed, throws an error. + * + * The mongosConnection parameter is optional and allows callers to specify a connection + * different than the first mongos instance in the list. + */ + this.awaitBalancerRound = function(timeoutMs, mongosConnection) { + timeoutMs = timeoutMs || 60000; + mongosConnection = mongosConnection || self.s0; + + // Get the balancer section from the server status of the config server primary + function getBalancerStatus() { + var balancerStatus = + assert.commandWorked(mongosConnection.adminCommand({balancerStatus: 1})); + if (balancerStatus.mode !== 'full') { + throw Error('Balancer is not enabled'); + } + + return balancerStatus; + } + + var initialStatus = getBalancerStatus(); + var currentStatus; + assert.soon( + function() { + currentStatus = getBalancerStatus(); + return (currentStatus.numBalancerRounds - initialStatus.numBalancerRounds) != 0; + }, + function() { + return 'Latest balancer status: ' + tojson(currentStatus); + }, + timeoutMs); + }; + + /** + * Waits up to one minute for the difference in chunks between the most loaded shard and + * least loaded shard to be 0 or 1, indicating that the collection is well balanced. This should + * only be called after creating a big enough chunk difference to trigger balancing. + */ + this.awaitBalance = function(collName, dbName, timeToWait) { + timeToWait = timeToWait || 60000; + + assert.soon(function() { + var x = self.chunkDiff(collName, dbName); + print("chunk diff: " + x); + return x < 2; + }, "no balance happened", timeToWait); }; this.getShard = function(coll, query, includeEmpty) { @@ -995,24 +1014,6 @@ var ShardingTest = function(params) { }; /** - * Waits for all operations to fully replicate on all shards. - */ - this.awaitReplicationOnShards = function() { - this._rs.forEach(replSet => replSet.test.awaitReplication()); - }; - - /** - * Query the oplog from a given node. - */ - ShardingTest.prototype.findOplog = function(conn, query, limit) { - return conn.getDB('local') - .getCollection(kOplogName) - .find(query) - .sort({$natural: -1}) - .limit(limit); - }; - - /** * Returns if there is a new feature compatibility version for the "latest" version. This must * be manually changed if and when there is a new feature compatibility version. */ @@ -1034,7 +1035,8 @@ var ShardingTest = function(params) { assert(isObject(params), 'ShardingTest configuration must be a JSON object'); var testName = params.name || jsTest.name(); - var otherParams = Object.deepMerge(params, params.other || {}); + var otherParams = Object.merge(params, params.other || {}); + var numShards = otherParams.hasOwnProperty('shards') ? otherParams.shards : 2; var mongosVerboseLevel = otherParams.hasOwnProperty('verbose') ? otherParams.verbose : 1; var numMongos = otherParams.hasOwnProperty('mongos') ? otherParams.mongos : 1; @@ -1184,551 +1186,537 @@ var ShardingTest = function(params) { randomSeedAlreadySet = true; } - try { - // - // Start each shard replica set. - // - let startTime = new Date(); // Measure the execution time of startup and initiate. - for (var i = 0; i < numShards; i++) { - var setName = testName + "-rs" + i; - - var rsDefaults = { - useHostname: otherParams.useHostname, - oplogSize: 16, - shardsvr: '', - pathOpts: Object.merge(pathOpts, {shard: i}), - }; + // + // Start each shard replica set. + // + let startTime = new Date(); // Measure the execution time of startup and initiate. + for (var i = 0; i < numShards; i++) { + var setName = testName + "-rs" + i; + + var rsDefaults = { + useHostname: otherParams.useHostname, + oplogSize: 16, + shardsvr: '', + pathOpts: Object.merge(pathOpts, {shard: i}), + }; - if (otherParams.rs || otherParams["rs" + i]) { - if (otherParams.rs) { - rsDefaults = Object.merge(rsDefaults, otherParams.rs); - } - if (otherParams["rs" + i]) { - rsDefaults = Object.merge(rsDefaults, otherParams["rs" + i]); - } - rsDefaults = Object.merge(rsDefaults, otherParams.rsOptions); - rsDefaults.nodes = rsDefaults.nodes || otherParams.numReplicas; - } else { - if (jsTestOptions().shardMixedBinVersions) { - if (!otherParams.shardOptions) { - otherParams.shardOptions = {}; - } - // If the test doesn't depend on specific shard binVersions, create a mixed - // version - // shard cluster that randomly assigns shard binVersions, half "latest" and half - // "last-continuous" or "last-lts". - // shardMixedBinVersions. - if (!otherParams.shardOptions.binVersion) { - Random.setRandomSeed(); - otherParams.shardOptions.binVersion = MongoRunner.versionIterator( - ["latest", jsTestOptions().shardMixedBinVersions], true); - } + if (otherParams.rs || otherParams["rs" + i]) { + if (otherParams.rs) { + rsDefaults = Object.merge(rsDefaults, otherParams.rs); + } + if (otherParams["rs" + i]) { + rsDefaults = Object.merge(rsDefaults, otherParams["rs" + i]); + } + rsDefaults = Object.merge(rsDefaults, otherParams.rsOptions); + rsDefaults.nodes = rsDefaults.nodes || otherParams.numReplicas; + } else { + if (jsTestOptions().shardMixedBinVersions) { + if (!otherParams.shardOptions) { + otherParams.shardOptions = {}; } - - if (otherParams.shardOptions && otherParams.shardOptions.binVersion) { - otherParams.shardOptions.binVersion = - MongoRunner.versionIterator(otherParams.shardOptions.binVersion); + // If the test doesn't depend on specific shard binVersions, create a mixed + // version + // shard cluster that randomly assigns shard binVersions, half "latest" and half + // "last-continuous" or "last-lts". + // shardMixedBinVersions. + if (!otherParams.shardOptions.binVersion) { + Random.setRandomSeed(); + otherParams.shardOptions.binVersion = MongoRunner.versionIterator( + ["latest", jsTestOptions().shardMixedBinVersions], true); } + } - rsDefaults = Object.merge(rsDefaults, otherParams["d" + i]); - rsDefaults = Object.merge(rsDefaults, otherParams.shardOptions); + if (otherParams.shardOptions && otherParams.shardOptions.binVersion) { + otherParams.shardOptions.binVersion = + MongoRunner.versionIterator(otherParams.shardOptions.binVersion); } - rsDefaults.setParameter = rsDefaults.setParameter || {}; - rsDefaults.setParameter.migrationLockAcquisitionMaxWaitMS = - otherParams.migrationLockAcquisitionMaxWaitMS; + rsDefaults = Object.merge(rsDefaults, otherParams["d" + i]); + rsDefaults = Object.merge(rsDefaults, otherParams.shardOptions); + } - var rsSettings = rsDefaults.settings; - delete rsDefaults.settings; + rsDefaults.setParameter = rsDefaults.setParameter || {}; + rsDefaults.setParameter.migrationLockAcquisitionMaxWaitMS = + otherParams.migrationLockAcquisitionMaxWaitMS; - // The number of nodes in the rs field will take priority. - if (otherParams.rs || otherParams["rs" + i]) { - var numReplicas = rsDefaults.nodes || 3; - } else { - var numReplicas = 1; - } - delete rsDefaults.nodes; - - var protocolVersion = rsDefaults.protocolVersion; - delete rsDefaults.protocolVersion; - - var rs = new ReplSetTest({ - name: setName, - nodes: numReplicas, - host: hostName, - useHostName: otherParams.useHostname, - useBridge: otherParams.useBridge, - bridgeOptions: otherParams.bridgeOptions, - keyFile: this.keyFile, - protocolVersion: protocolVersion, - waitForKeys: false, - settings: rsSettings, - seedRandomNumberGenerator: !randomSeedAlreadySet, - }); + var rsSettings = rsDefaults.settings; + delete rsDefaults.settings; - print("ShardingTest starting replica set for shard: " + setName); - - // Start up the replica set but don't wait for it to complete. This allows the startup - // of each shard to proceed in parallel. - this._rs[i] = - {setName: setName, test: rs, nodes: rs.startSetAsync(rsDefaults), url: rs.getURL()}; + // The number of nodes in the rs field will take priority. + if (otherParams.rs || otherParams["rs" + i]) { + var numReplicas = rsDefaults.nodes || 3; + } else { + var numReplicas = 1; } + delete rsDefaults.nodes; - // - // Start up the config server replica set. - // + var protocolVersion = rsDefaults.protocolVersion; + delete rsDefaults.protocolVersion; - var rstOptions = { - useHostName: otherParams.useHostname, + var rs = new ReplSetTest({ + name: setName, + nodes: numReplicas, host: hostName, + useHostName: otherParams.useHostname, useBridge: otherParams.useBridge, bridgeOptions: otherParams.bridgeOptions, keyFile: this.keyFile, + protocolVersion: protocolVersion, waitForKeys: false, - name: testName + "-configRS", + settings: rsSettings, seedRandomNumberGenerator: !randomSeedAlreadySet, - isConfigServer: true, - }; + }); - // always use wiredTiger as the storage engine for CSRS - var startOptions = { - pathOpts: pathOpts, - // Ensure that journaling is always enabled for config servers. - journal: "", - configsvr: "", - storageEngine: "wiredTiger", - }; + print("ShardingTest starting replica set for shard: " + setName); - if (otherParams.configOptions && otherParams.configOptions.binVersion) { - otherParams.configOptions.binVersion = - MongoRunner.versionIterator(otherParams.configOptions.binVersion); - } + // Start up the replica set but don't wait for it to complete. This allows the startup + // of each shard to proceed in parallel. + this._rs[i] = + {setName: setName, test: rs, nodes: rs.startSetAsync(rsDefaults), url: rs.getURL()}; + } - startOptions = Object.merge(startOptions, otherParams.configOptions); - rstOptions = Object.merge(rstOptions, otherParams.configReplSetTestOptions); + // + // Start up the config server replica set. + // + + var rstOptions = { + useHostName: otherParams.useHostname, + host: hostName, + useBridge: otherParams.useBridge, + bridgeOptions: otherParams.bridgeOptions, + keyFile: this.keyFile, + waitForKeys: false, + name: testName + "-configRS", + seedRandomNumberGenerator: !randomSeedAlreadySet, + isConfigServer: true, + }; - var nodeOptions = []; - for (var i = 0; i < numConfigs; ++i) { - nodeOptions.push(otherParams["c" + i] || {}); - } + // always use wiredTiger as the storage engine for CSRS + var startOptions = { + pathOpts: pathOpts, + // Ensure that journaling is always enabled for config servers. + journal: "", + configsvr: "", + storageEngine: "wiredTiger", + }; - rstOptions.nodes = nodeOptions; + if (otherParams.configOptions && otherParams.configOptions.binVersion) { + otherParams.configOptions.binVersion = + MongoRunner.versionIterator(otherParams.configOptions.binVersion); + } - // Start the config server's replica set without waiting for it to complete. This allows it - // to proceed in parallel with the startup of each shard. - this.configRS = new ReplSetTest(rstOptions); - this.configRS.startSetAsync(startOptions); + startOptions = Object.merge(startOptions, otherParams.configOptions); + rstOptions = Object.merge(rstOptions, otherParams.configReplSetTestOptions); - // - // Wait for each shard replica set to finish starting up. - // - for (let i = 0; i < numShards; i++) { - print("Waiting for shard " + this._rs[i].setName + " to finish starting up."); - this._rs[i].test.startSetAwait(); - } - - // - // Wait for the config server to finish starting up. - // - print("Waiting for the config server to finish starting up."); - this.configRS.startSetAwait(); - var config = this.configRS.getReplSetConfig(); - config.configsvr = true; - config.settings = config.settings || {}; - - print("ShardingTest startup for all nodes took " + (new Date() - startTime) + "ms with " + - this.configRS.nodeList().length + " config server nodes and " + totalNumShardNodes() + - " total shard nodes."); - - // - // Initiate each shard replica set and wait for replication. Also initiate the config - // replica set. Whenever possible, in parallel. - // - const shardsRS = this._rs.map(obj => obj.test); - const replicaSetsToInitiate = [...shardsRS, this.configRS].map(rst => { - const rstConfig = rst.getReplSetConfig(); - - // The mongo shell cannot authenticate as the internal __system user in tests that use - // x509 for cluster authentication. Choosing the default value for - // wcMajorityJournalDefault in ReplSetTest cannot be done automatically without the - // shell performing such authentication, so allow tests to pass the value in. - if (otherParams.hasOwnProperty("writeConcernMajorityJournalDefault")) { - rstConfig.writeConcernMajorityJournalDefault = - otherParams.writeConcernMajorityJournalDefault; - } + var nodeOptions = []; + for (var i = 0; i < numConfigs; ++i) { + nodeOptions.push(otherParams["c" + i] || {}); + } - if (rst === this.configRS) { - rstConfig.configsvr = true; - rstConfig.writeConcernMajorityJournalDefault = true; - } + rstOptions.nodes = nodeOptions; - return { - rst, - // Arguments for creating instances of each replica set within parallel threads. - rstArgs: { - name: rst.name, - nodeHosts: rst.nodes.map(node => `127.0.0.1:${node.port}`), - nodeOptions: rst.nodeOptions, - // Mixed-mode SSL tests may specify a keyFile per replica set rather than one - // for the whole cluster. - keyFile: rst.keyFile ? rst.keyFile : this.keyFile, - host: otherParams.useHostname ? hostName : "localhost", - waitForKeys: false, - }, - // Replica set configuration for initiating the replica set. - rstConfig, - }; - }); + // Start the config server's replica set without waiting for it to complete. This allows it + // to proceed in parallel with the startup of each shard. + this.configRS = new ReplSetTest(rstOptions); + this.configRS.startSetAsync(startOptions); - const initiateReplicaSet = (rst, rstConfig) => { - rst.initiateWithAnyNodeAsPrimary(rstConfig); + // + // Wait for each shard replica set to finish starting up. + // + for (let i = 0; i < numShards; i++) { + print("Waiting for shard " + this._rs[i].setName + " to finish starting up."); + this._rs[i].test.startSetAwait(); + } - // Do replication. - rst.awaitNodesAgreeOnPrimary(); - rst.getPrimary().getDB("admin").foo.save({x: 1}); - if (rst.keyFile) { - authutil.asCluster(rst.nodes, rst.keyFile, function() { - rst.awaitReplication(); - }); - } - rst.awaitSecondaryNodes(); + // + // Wait for the config server to finish starting up. + // + print("Waiting for the config server to finish starting up."); + this.configRS.startSetAwait(); + var config = this.configRS.getReplSetConfig(); + config.configsvr = true; + config.settings = config.settings || {}; + + print("ShardingTest startup for all nodes took " + (new Date() - startTime) + "ms with " + + this.configRS.nodeList().length + " config server nodes and " + totalNumShardNodes() + + " total shard nodes."); + + // + // Initiate each shard replica set and wait for replication. Also initiate the config replica + // set. Whenever possible, in parallel. + // + const shardsRS = this._rs.map(obj => obj.test); + const replicaSetsToInitiate = [...shardsRS, this.configRS].map(rst => { + const rstConfig = rst.getReplSetConfig(); + + // The mongo shell cannot authenticate as the internal __system user in tests that use x509 + // for cluster authentication. Choosing the default value for wcMajorityJournalDefault in + // ReplSetTest cannot be done automatically without the shell performing such + // authentication, so allow tests to pass the value in. + if (otherParams.hasOwnProperty("writeConcernMajorityJournalDefault")) { + rstConfig.writeConcernMajorityJournalDefault = + otherParams.writeConcernMajorityJournalDefault; + } + + if (rst === this.configRS) { + rstConfig.configsvr = true; + rstConfig.writeConcernMajorityJournalDefault = true; + } + + return { + rst, + // Arguments for creating instances of each replica set within parallel threads. + rstArgs: { + name: rst.name, + nodeHosts: rst.nodes.map(node => `127.0.0.1:${node.port}`), + nodeOptions: rst.nodeOptions, + // Mixed-mode SSL tests may specify a keyFile per replica set rather than one for + // the whole cluster. + keyFile: rst.keyFile ? rst.keyFile : this.keyFile, + host: otherParams.useHostname ? hostName : "localhost", + waitForKeys: false, + }, + // Replica set configuration for initiating the replica set. + rstConfig, }; + }); - const isParallelSupported = (() => { - if (!tryLoadParallelTester()) { - return false; - } + const initiateReplicaSet = (rst, rstConfig) => { + rst.initiateWithAnyNodeAsPrimary(rstConfig); - for (let {rst} of replicaSetsToInitiate) { - if (rst.startOptions && rst.startOptions.clusterAuthMode === "x509") { - // The mongo shell performing X.509 authentication as a cluster member requires - // starting a parallel shell and using the server's (not the client's) - // certificate. The ReplSetTest instance constructed in a Thread wouldn't have - // copied the path to the server's certificate. We therefore fall back to - // initiating the CSRS and replica set shards sequentially when X.509 - // authentication is being used. - return false; - } + // Do replication. + rst.awaitNodesAgreeOnPrimary(); + rst.getPrimary().getDB("admin").foo.save({x: 1}); + if (rst.keyFile) { + authutil.asCluster(rst.nodes, rst.keyFile, function() { + rst.awaitReplication(); + }); + } + rst.awaitSecondaryNodes(); + }; - for (let n of Object.keys(rst.nodeOptions)) { - const nodeOptions = rst.nodeOptions[n]; - if (nodeOptions && nodeOptions.clusterAuthMode === "x509") { - return false; - } - } - } + const isParallelSupported = (() => { + if (!tryLoadParallelTester()) { + return false; + } - return true; - })(); + for (let {rst} of replicaSetsToInitiate) { + if (rst.startOptions && rst.startOptions.clusterAuthMode === "x509") { + // The mongo shell performing X.509 authentication as a cluster member requires + // starting a parallel shell and using the server's (not the client's) certificate. + // The ReplSetTest instance constructed in a Thread wouldn't have copied the path to + // the server's certificate. We therefore fall back to initiating the CSRS and + // replica set shards sequentially when X.509 authentication is being used. + return false; + } - if (isParallelSupported) { - const threads = []; - try { - for (let {rstArgs, rstConfig} of replicaSetsToInitiate) { - const thread = new Thread((rstArgs, rstConfig, initiateReplicaSet) => { - try { - const rst = new ReplSetTest({rstArgs}); - initiateReplicaSet(rst, rstConfig); - return {ok: 1}; - } catch (e) { - return { - ok: 0, - hosts: rstArgs.nodeHosts, - name: rstArgs.name, - error: e.toString(), - stack: e.stack, - }; - } - }, rstArgs, rstConfig, initiateReplicaSet); - thread.start(); - threads.push(thread); + for (let n of Object.keys(rst.nodeOptions)) { + const nodeOptions = rst.nodeOptions[n]; + if (nodeOptions && nodeOptions.clusterAuthMode === "x509") { + return false; } - } 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, 'Initiating shard or config servers as a replica set failed'); - }); - } - } else { - for (let {rst, rstConfig} of replicaSetsToInitiate) { - initiateReplicaSet(rst, rstConfig); } } - for (let i = 0; i < numShards; i++) { - let rs = this._rs[i].test; - - this["rs" + i] = rs; - this._rsObjects[i] = rs; - - this._connections.push(null); + return true; + })(); - let rsConn = new Mongo(rs.getURL()); - rsConn.name = rs.getURL(); + if (isParallelSupported) { + const threads = []; + try { + for (let {rstArgs, rstConfig} of replicaSetsToInitiate) { + const thread = new Thread((rstArgs, rstConfig, initiateReplicaSet) => { + try { + const rst = new ReplSetTest({rstArgs}); + initiateReplicaSet(rst, rstConfig); + return {ok: 1}; + } catch (e) { + return { + ok: 0, + hosts: rstArgs.nodeHosts, + name: rstArgs.name, + error: e.toString(), + stack: e.stack, + }; + } + }, rstArgs, rstConfig, initiateReplicaSet); + thread.start(); + threads.push(thread); + } + } finally { + // Wait for each thread to finish. Throw an error if any thread fails. + const returnData = threads.map(thread => { + thread.join(); + return thread.returnData(); + }); - this._connections[i] = rsConn; - this["shard" + i] = rsConn; - rsConn.rs = rs; + returnData.forEach(res => { + assert.commandWorked(res, + 'Initiating shard or config servers as a replica set failed'); + }); + } + } else { + for (let {rst, rstConfig} of replicaSetsToInitiate) { + initiateReplicaSet(rst, rstConfig); } + } - // Wait for master to be elected before starting mongos - this.configRS.awaitNodesAgreeOnPrimary(); - var csrsPrimary = this.configRS.getPrimary(); + for (let i = 0; i < numShards; i++) { + let rs = this._rs[i].test; - print("ShardingTest startup and initiation for all nodes took " + (new Date() - startTime) + - "ms with " + this.configRS.nodeList().length + " config server nodes and " + - totalNumShardNodes() + " total shard nodes."); + this["rs" + i] = rs; + this._rsObjects[i] = rs; - // If 'otherParams.mongosOptions.binVersion' is an array value, then we'll end up - // constructing a version iterator. - const mongosOptions = []; - for (var i = 0; i < numMongos; ++i) { - let options = { - useHostname: otherParams.useHostname, - pathOpts: Object.merge(pathOpts, {mongos: i}), - verbose: mongosVerboseLevel, - keyFile: this.keyFile, - }; + this._connections.push(null); - if (otherParams.mongosOptions && otherParams.mongosOptions.binVersion) { - otherParams.mongosOptions.binVersion = - MongoRunner.versionIterator(otherParams.mongosOptions.binVersion); - } + let rsConn = new Mongo(rs.getURL()); + rsConn.name = rs.getURL(); - options = Object.merge(options, otherParams.mongosOptions); - options = Object.merge(options, otherParams["s" + i]); - - // The default time for mongos quiesce mode in response to SIGTERM is 15 seconds. - // Reduce this to 0 for faster shutdown. - options.setParameter = options.setParameter || {}; - options.setParameter.mongosShutdownTimeoutMillisForSignaledShutdown = - options.setParameter.mongosShutdownTimeoutMillisForSignaledShutdown || 0; + this._connections[i] = rsConn; + this["shard" + i] = rsConn; + rsConn.rs = rs; + } - options.port = options.port || _allocatePortForMongos(); + // Wait for master to be elected before starting mongos + this.configRS.awaitNodesAgreeOnPrimary(); + var csrsPrimary = this.configRS.getPrimary(); + + print("ShardingTest startup and initiation for all nodes took " + (new Date() - startTime) + + "ms with " + this.configRS.nodeList().length + " config server nodes and " + + totalNumShardNodes() + " total shard nodes."); + + // If 'otherParams.mongosOptions.binVersion' is an array value, then we'll end up constructing a + // version iterator. + const mongosOptions = []; + for (var i = 0; i < numMongos; ++i) { + let options = { + useHostname: otherParams.useHostname, + pathOpts: Object.merge(pathOpts, {mongos: i}), + verbose: mongosVerboseLevel, + keyFile: this.keyFile, + }; - mongosOptions.push(options); + if (otherParams.mongosOptions && otherParams.mongosOptions.binVersion) { + otherParams.mongosOptions.binVersion = + MongoRunner.versionIterator(otherParams.mongosOptions.binVersion); } - const configRS = this.configRS; - const clusterVersionInfo = this.getClusterVersionInfo(); - if (_hasNewFeatureCompatibilityVersion() && clusterVersionInfo.isMixedVersion) { - const fcv = binVersionToFCV(clusterVersionInfo.oldestBinVersion); - function setFeatureCompatibilityVersion() { - assert.commandWorked(csrsPrimary.adminCommand( - {setFeatureCompatibilityVersion: fcv, fromConfigServer: true})); + options = Object.merge(options, otherParams.mongosOptions); + options = Object.merge(options, otherParams["s" + i]); - // Wait for the new featureCompatibilityVersion to propagate to all nodes in the - // CSRS to ensure that older versions of mongos can successfully connect. - configRS.awaitReplication(); - } + // The default time for mongos quiesce mode in response to SIGTERM is 15 seconds. + // Reduce this to 0 for faster shutdown. + options.setParameter = options.setParameter || {}; + options.setParameter.mongosShutdownTimeoutMillisForSignaledShutdown = + options.setParameter.mongosShutdownTimeoutMillisForSignaledShutdown || 0; - if (this.keyFile) { - authutil.asCluster( - this.configRS.nodes, this.keyFile, setFeatureCompatibilityVersion); - } else { - setFeatureCompatibilityVersion(); - } - } + options.port = options.port || _allocatePortForMongos(); - // If chunkSize has been requested for this test, write the configuration - if (otherParams.chunkSize) { - function setChunkSize() { - assert.commandWorked(csrsPrimary.getDB('config').settings.update( - {_id: 'chunksize'}, - {$set: {value: otherParams.chunkSize}}, - {upsert: true, writeConcern: {w: 'majority', wtimeout: kDefaultWTimeoutMs}})); + mongosOptions.push(options); + } - configRS.awaitLastOpCommitted(); - } + const configRS = this.configRS; + const clusterVersionInfo = this.getClusterVersionInfo(); + if (_hasNewFeatureCompatibilityVersion() && clusterVersionInfo.isMixedVersion) { + const fcv = binVersionToFCV(clusterVersionInfo.oldestBinVersion); + function setFeatureCompatibilityVersion() { + assert.commandWorked(csrsPrimary.adminCommand( + {setFeatureCompatibilityVersion: fcv, fromConfigServer: true})); - if (this.keyFile) { - authutil.asCluster(csrsPrimary, this.keyFile, setChunkSize); - } else { - setChunkSize(); - } + // Wait for the new featureCompatibilityVersion to propagate to all nodes in the CSRS + // to ensure that older versions of mongos can successfully connect. + configRS.awaitReplication(); } - this._configDB = this.configRS.getURL(); - for (var i = 0; i < numConfigs; ++i) { - var conn = this.configRS.nodes[i]; - this["config" + i] = conn; - this["c" + i] = conn; + if (this.keyFile) { + authutil.asCluster(this.configRS.nodes, this.keyFile, setFeatureCompatibilityVersion); + } else { + setFeatureCompatibilityVersion(); } + } - printjson('Config servers: ' + this._configDB); + // If chunkSize has been requested for this test, write the configuration + if (otherParams.chunkSize) { + function setChunkSize() { + assert.commandWorked(csrsPrimary.getDB('config').settings.update( + {_id: 'chunksize'}, + {$set: {value: otherParams.chunkSize}}, + {upsert: true, writeConcern: {w: 'majority', wtimeout: kDefaultWTimeoutMs}})); - print("ShardingTest " + this._testName + " :\n" + - tojson({config: this._configDB, shards: this._connections})); + configRS.awaitLastOpCommitted(); + } - this._mongos = []; + if (this.keyFile) { + authutil.asCluster(csrsPrimary, this.keyFile, setChunkSize); + } else { + setChunkSize(); + } + } - // Start the MongoS servers - for (var i = 0; i < numMongos; i++) { - const options = mongosOptions[i]; - options.configdb = this._configDB; + this._configDB = this.configRS.getURL(); + for (var i = 0; i < numConfigs; ++i) { + var conn = this.configRS.nodes[i]; + this["config" + i] = conn; + this["c" + i] = conn; + } - if (otherParams.useBridge) { - var bridgeOptions = - Object.merge(otherParams.bridgeOptions, options.bridgeOptions || {}); - bridgeOptions = Object.merge(bridgeOptions, { - hostName: otherParams.useHostname ? hostName : "localhost", - port: _allocatePortForBridgeForMongos(), - // The mongos processes identify themselves to mongobridge as host:port, where - // the host is the actual hostname of the machine and not localhost. - dest: hostName + ":" + options.port, - }); + printjson('Config servers: ' + this._configDB); - var bridge = new MongoBridge(bridgeOptions); - } + print("ShardingTest " + this._testName + " :\n" + + tojson({config: this._configDB, shards: this._connections})); - var conn = MongoRunner.runMongos(options); - if (!conn) { - throw new Error("Failed to start mongos " + i); - } + this._mongos = []; - if (otherParams.causallyConsistent) { - conn.setCausalConsistency(true); - } + // Start the MongoS servers + for (var i = 0; i < numMongos; i++) { + const options = mongosOptions[i]; + options.configdb = this._configDB; - if (otherParams.useBridge) { - bridge.connectToBridge(); - this._mongos.push(bridge); - unbridgedMongos.push(conn); - } else { - this._mongos.push(conn); - } + if (otherParams.useBridge) { + var bridgeOptions = + Object.merge(otherParams.bridgeOptions, options.bridgeOptions || {}); + bridgeOptions = Object.merge(bridgeOptions, { + hostName: otherParams.useHostname ? hostName : "localhost", + port: _allocatePortForBridgeForMongos(), + // The mongos processes identify themselves to mongobridge as host:port, where the + // host is the actual hostname of the machine and not localhost. + dest: hostName + ":" + options.port, + }); - if (i === 0) { - this.s = this._mongos[i]; - this.admin = this._mongos[i].getDB('admin'); - this.config = this._mongos[i].getDB('config'); - } + var bridge = new MongoBridge(bridgeOptions); + } - this["s" + i] = this._mongos[i]; + var conn = MongoRunner.runMongos(options); + if (!conn) { + throw new Error("Failed to start mongos " + i); } - _extendWithShMethods(); + if (otherParams.causallyConsistent) { + conn.setCausalConsistency(true); + } - // If auth is enabled for the test, login the mongos connections as system in order to - // configure the instances and then log them out again. - if (this.keyFile) { - authutil.asCluster(this._mongos, this.keyFile, _configureCluster); - } else if (mongosOptions[0] && mongosOptions[0].keyFile) { - authutil.asCluster(this._mongos, mongosOptions[0].keyFile, _configureCluster); + if (otherParams.useBridge) { + bridge.connectToBridge(); + this._mongos.push(bridge); + unbridgedMongos.push(conn); } else { - _configureCluster(); - // Ensure that all config server nodes are up to date with any changes made to balancer - // settings before adding shards to the cluster. This prevents shards, which read - // config.settings with readPreference 'nearest', from accidentally fetching stale - // values from secondaries that aren't up-to-date. - this.configRS.awaitLastOpCommitted(); + this._mongos.push(conn); } - try { - if (!otherParams.manualAddShard) { - var testName = this._testName; - var admin = this.admin; + if (i === 0) { + this.s = this._mongos[i]; + this.admin = this._mongos[i].getDB('admin'); + this.config = this._mongos[i].getDB('config'); + } - this._connections.forEach(function(z) { - var n = z.name || z.host || z; + this["s" + i] = this._mongos[i]; + } - print("ShardingTest " + testName + " going to add shard : " + n); + _extendWithShMethods(); - var result = assert.commandWorked(admin.runCommand({addshard: n}), - "Failed to add shard " + n); - z.shardName = result.shardAdded; - }); - } - } catch (e) { - // Clean up the running procceses on failure - print("Failed to add shards, stopping cluster."); - this.stop(); - throw e; - } - - // Ensure that the sessions collection exists so jstests can run things with - // logical sessions and test them. We do this by forcing an immediate cache refresh - // on the config server, which auto-shards the collection for the cluster. - this.configRS.getPrimary().getDB("admin").runCommand({refreshLogicalSessionCacheNow: 1}); - - // Ensure that all CSRS nodes are up to date. This is strictly needed for tests that use - // multiple mongoses. In those cases, the first mongos initializes the contents of the - // 'config' database, but without waiting for those writes to replicate to all the config - // servers then the secondary mongoses risk reading from a stale config server and seeing an - // empty config database. + // If auth is enabled for the test, login the mongos connections as system in order to configure + // the instances and then log them out again. + if (this.keyFile) { + authutil.asCluster(this._mongos, this.keyFile, _configureCluster); + } else if (mongosOptions[0] && mongosOptions[0].keyFile) { + authutil.asCluster(this._mongos, mongosOptions[0].keyFile, _configureCluster); + } else { + _configureCluster(); + // Ensure that all config server nodes are up to date with any changes made to balancer + // settings before adding shards to the cluster. This prevents shards, which read + // config.settings with readPreference 'nearest', from accidentally fetching stale values + // from secondaries that aren't up-to-date. this.configRS.awaitLastOpCommitted(); + } + + try { + if (!otherParams.manualAddShard) { + var testName = this._testName; + var admin = this.admin; + + this._connections.forEach(function(z) { + var n = z.name || z.host || z; - if (jsTestOptions().keyFile) { - jsTest.authenticateNodes(this._mongos); + print("ShardingTest " + testName + " going to add shard : " + n); + + var result = assert.commandWorked(admin.runCommand({addshard: n}), + "Failed to add shard " + n); + z.shardName = result.shardAdded; + }); } + } catch (e) { + // Clean up the running procceses on failure + print("Failed to add shards, stopping cluster."); + this.stop(); + throw e; + } - // Flushes the routing table cache on connection 'conn'. If 'keyFileLocal' is defined, - // authenticates the keyfile user. - const flushRT = function flushRoutingTableAndHandleAuth(conn, keyFileLocal) { - // Invokes the actual execution of cache refresh. - const execFlushRT = (conn) => { - assert.commandWorked(conn.getDB("admin").runCommand( - {_flushRoutingTableCacheUpdates: "config.system.sessions"})); - }; + // Ensure that the sessions collection exists so jstests can run things with + // logical sessions and test them. We do this by forcing an immediate cache refresh + // on the config server, which auto-shards the collection for the cluster. + this.configRS.getPrimary().getDB("admin").runCommand({refreshLogicalSessionCacheNow: 1}); - const x509AuthRequired = (conn.fullOptions && conn.fullOptions.clusterAuthMode && - conn.fullOptions.clusterAuthMode === "x509"); - - if (keyFileLocal) { - authutil.asCluster(conn, keyFileLocal, () => execFlushRT(conn)); - } else if (x509AuthRequired) { - const exitCode = - _runMongoProgram(...["mongo", - conn.host, - "--tls", - "--tlsAllowInvalidHostnames", - "--tlsCertificateKeyFile", - conn.fullOptions.tlsCertificateKeyFile - ? conn.fullOptions.tlsCertificateKeyFile - : conn.fullOptions.sslPEMKeyFile, - "--tlsCAFile", - conn.fullOptions.tlsCAFile ? conn.fullOptions.tlsCAFile - : conn.fullOptions.sslCAFile, - "--authenticationDatabase=$external", - "--authenticationMechanism=MONGODB-X509", - "--eval", - `(${execFlushRT.toString()})(db.getMongo())`, - ]); - assert.eq(0, exitCode, "parallel shell for x509 auth failed"); - } else { - execFlushRT(conn); - } - }; + // Ensure that all CSRS nodes are up to date. This is strictly needed for tests that use + // multiple mongoses. In those cases, the first mongos initializes the contents of the 'config' + // database, but without waiting for those writes to replicate to all the config servers then + // the secondary mongoses risk reading from a stale config server and seeing an empty config + // database. + this.configRS.awaitLastOpCommitted(); - if (!otherParams.manualAddShard) { - for (let i = 0; i < numShards; i++) { - const keyFileLocal = - (otherParams.shards && otherParams.shards[i] && otherParams.shards[i].keyFile) - ? otherParams.shards[i].keyFile - : this.keyFile; - - const rs = this._rs[i].test; - flushRT(rs.getPrimary(), keyFileLocal); - } + if (jsTestOptions().keyFile) { + jsTest.authenticateNodes(this._mongos); + } + + // Flushes the routing table cache on connection 'conn'. If 'keyFileLocal' is defined, + // authenticates the keyfile user. + const flushRT = function flushRoutingTableAndHandleAuth(conn, keyFileLocal) { + // Invokes the actual execution of cache refresh. + const execFlushRT = (conn) => { + assert.commandWorked(conn.getDB("admin").runCommand( + {_flushRoutingTableCacheUpdates: "config.system.sessions"})); + }; - self.waitForShardingInitialized(); + const x509AuthRequired = (conn.fullOptions && conn.fullOptions.clusterAuthMode && + conn.fullOptions.clusterAuthMode === "x509"); + + if (keyFileLocal) { + authutil.asCluster(conn, keyFileLocal, () => execFlushRT(conn)); + } else if (x509AuthRequired) { + const exitCode = _runMongoProgram( + ...["mongo", + conn.host, + "--tls", + "--tlsAllowInvalidHostnames", + "--tlsCertificateKeyFile", + conn.fullOptions.tlsCertificateKeyFile ? conn.fullOptions.tlsCertificateKeyFile + : conn.fullOptions.sslPEMKeyFile, + "--tlsCAFile", + conn.fullOptions.tlsCAFile ? conn.fullOptions.tlsCAFile + : conn.fullOptions.sslCAFile, + "--authenticationDatabase=$external", + "--authenticationMechanism=MONGODB-X509", + "--eval", + `(${execFlushRT.toString()})(db.getMongo())`, + ]); + assert.eq(0, exitCode, "parallel shell for x509 auth failed"); + } else { + execFlushRT(conn); } - } catch (e) { - // this was expected to fail, so clean up appropriately - if (params.shouldFailInit === true) { - this.stopOnFail(); + }; + + if (!otherParams.manualAddShard) { + for (let i = 0; i < numShards; i++) { + const keyFileLocal = + (otherParams.shards && otherParams.shards[i] && otherParams.shards[i].keyFile) + ? otherParams.shards[i].keyFile + : this.keyFile; + + const rs = this._rs[i].test; + flushRT(rs.getPrimary(), keyFileLocal); } - throw e; + + self.waitForShardingInitialized(); } - // This initialization was expected to fail, but it did not. - assert.neq( - true, params.shouldFailInit, "This was expected to fail initialization, but it did not"); }; // Stub for a hook to check that collection UUIDs are consistent across shards and the config |
