diff options
Diffstat (limited to 'src/mongo/shell')
| -rw-r--r-- | src/mongo/shell/assert.js | 3 | ||||
| -rw-r--r-- | src/mongo/shell/db.js | 51 | ||||
| -rw-r--r-- | src/mongo/shell/encrypted_dbclient_base.cpp | 77 | ||||
| -rw-r--r-- | src/mongo/shell/encrypted_dbclient_base.h | 60 | ||||
| -rw-r--r-- | src/mongo/shell/keyvault.js | 71 | ||||
| -rw-r--r-- | src/mongo/shell/linenoise.cpp | 5 | ||||
| -rw-r--r-- | src/mongo/shell/mongo_main.cpp | 10 | ||||
| -rw-r--r-- | src/mongo/shell/replsettest.js | 43 | ||||
| -rw-r--r-- | src/mongo/shell/servers.js | 13 | ||||
| -rw-r--r-- | src/mongo/shell/shardingtest.js | 977 | ||||
| -rw-r--r-- | src/mongo/shell/shell_utils.cpp | 22 | ||||
| -rw-r--r-- | src/mongo/shell/utils.js | 46 | ||||
| -rw-r--r-- | src/mongo/shell/utils_sh.js | 141 |
13 files changed, 853 insertions, 666 deletions
diff --git a/src/mongo/shell/assert.js b/src/mongo/shell/assert.js index ff168bd865d..f0cb2acdfc8 100644 --- a/src/mongo/shell/assert.js +++ b/src/mongo/shell/assert.js @@ -110,7 +110,8 @@ assert = (function() { doassert("msg function cannot expect any parameters."); } } else if (typeof msg !== "string" && typeof msg !== "object") { - doassert("msg parameter must be a string, function or object."); + doassert("msg parameter must be a string, function or object. Found type: " + + typeof (msg)); } if (msg && assert._debug) { diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index d3800871af1..2364460d62a 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -1745,57 +1745,6 @@ DB.prototype.watch = function(pipeline, options) { return this._runAggregate({aggregate: 1, pipeline: pipeline}, aggOptions); }; -DB.prototype.getFreeMonitoringStatus = function() { - 'use strict'; - return assert.commandWorked(this.adminCommand({getFreeMonitoringStatus: 1})); -}; - -DB.prototype.enableFreeMonitoring = function() { - 'use strict'; - let reply, isPrimary; - if (this.getMongo().getApiParameters().apiVersion) { - reply = this.hello(); - isPrimary = reply.isWritablePrimary; - } else { - reply = this.isMaster(); - isPrimary = reply.ismaster; - } - - if (!isPrimary) { - print("ERROR: db.enableFreeMonitoring() may only be run on a primary"); - return; - } - - assert.commandWorked(this.adminCommand({setFreeMonitoring: 1, action: 'enable'})); - - const cmd = this.adminCommand({getFreeMonitoringStatus: 1}); - if (!cmd.ok && (cmd.code == ErrorCode.Unauthorized)) { - // Edge case: It's technically possible that a user can change free-mon state, - // but is not allowed to inspect it. - print("Successfully initiated free monitoring, but unable to determine status " + - "as you lack the 'checkFreeMonitoringStatus' privilege."); - return; - } - assert.commandWorked(cmd); - - if (cmd.state !== 'enabled') { - const url = this.adminCommand({'getParameter': 1, 'cloudFreeMonitoringEndpointURL': 1}) - .cloudFreeMonitoringEndpointURL; - - print("Unable to get immediate response from the Cloud Monitoring service. We will" + - "continue to retry in the background. Please check your firewall " + - "settings to ensure that mongod can communicate with \"" + url + "\""); - return; - } - - print(tojson(cmd)); -}; - -DB.prototype.disableFreeMonitoring = function() { - 'use strict'; - assert.commandWorked(this.adminCommand({setFreeMonitoring: 1, action: 'disable'})); -}; - // Writing `this.hasOwnProperty` would cause DB.prototype.getCollection() to be called since the // DB's getProperty() handler in C++ takes precedence when a property isn't defined on the DB // instance directly. The "hasOwnProperty" property is defined on Object.prototype, so we must diff --git a/src/mongo/shell/encrypted_dbclient_base.cpp b/src/mongo/shell/encrypted_dbclient_base.cpp index 137d0c482ab..b9151685a93 100644 --- a/src/mongo/shell/encrypted_dbclient_base.cpp +++ b/src/mongo/shell/encrypted_dbclient_base.cpp @@ -199,22 +199,24 @@ void EncryptedDBClientBase::decryptPayload(ConstDataRange data, } } -std::pair<rpc::UniqueReply, DBClientBase*> EncryptedDBClientBase::processResponseFLE1( - rpc::UniqueReply result, const StringData databaseName) { - auto rawReply = result->getCommandReply(); +EncryptedDBClientBase::RunCommandReturn EncryptedDBClientBase::processResponseFLE1( + EncryptedDBClientBase::RunCommandReturn result, const StringData databaseName) { + auto rawReply = result.returnReply->getCommandReply(); return prepareReply( std::move(result), databaseName, encryptDecryptCommand(rawReply, false, databaseName)); } -std::pair<rpc::UniqueReply, DBClientBase*> EncryptedDBClientBase::processResponseFLE2( - rpc::UniqueReply result, const StringData databaseName) { - auto rawReply = result->getCommandReply(); +EncryptedDBClientBase::RunCommandReturn EncryptedDBClientBase::processResponseFLE2( + EncryptedDBClientBase::RunCommandReturn result, const StringData databaseName) { + auto rawReply = result.returnReply->getCommandReply(); return prepareReply( std::move(result), databaseName, FLEClientCrypto::decryptDocument(rawReply, this)); } -std::pair<rpc::UniqueReply, DBClientBase*> EncryptedDBClientBase::prepareReply( - rpc::UniqueReply result, const StringData databaseName, BSONObj decryptedDoc) { +EncryptedDBClientBase::RunCommandReturn EncryptedDBClientBase::prepareReply( + EncryptedDBClientBase::RunCommandReturn result, + const StringData databaseName, + BSONObj decryptedDoc) { rpc::OpMsgReplyBuilder replyBuilder; replyBuilder.setCommandReply(StatusWith<BSONObj>(decryptedDoc)); auto msg = replyBuilder.done(); @@ -222,22 +224,49 @@ std::pair<rpc::UniqueReply, DBClientBase*> EncryptedDBClientBase::prepareReply( auto host = _conn->getServerAddress(); auto reply = _conn->parseCommandReplyMessage(host, msg); - return {std::move(reply), this}; + return EncryptedDBClientBase::RunCommandReturn({std::move(reply), result}); } -std::pair<rpc::UniqueReply, DBClientBase*> EncryptedDBClientBase::runCommandWithTarget( - OpMsgRequest request) { - std::string commandName = request.getCommandName().toString(); - std::string databaseName = request.getDatabase().toString(); +EncryptedDBClientBase::RunCommandReturn EncryptedDBClientBase::doRunCommand( + EncryptedDBClientBase::RunCommandParams params) { + if (params.type == EncryptedDBClientBase::RunCommandConnectionType::rawPtr) { + return EncryptedDBClientBase::RunCommandReturn( + _conn->runCommandWithTarget(std::move(params.request))); + } + invariant(params.conn); + return EncryptedDBClientBase::RunCommandReturn( + _conn->runCommandWithTarget(std::move(params.request), params.conn)); +} + +EncryptedDBClientBase::RunCommandReturn EncryptedDBClientBase::handleEncryptionRequest( + EncryptedDBClientBase::RunCommandParams params) { + auto commandName = params.request.getCommandName().toString(); + auto databaseName = params.request.getDatabase().toString(); if (std::find(kEncryptedCommands.begin(), kEncryptedCommands.end(), StringData(commandName)) == std::end(kEncryptedCommands)) { - return _conn->runCommandWithTarget(std::move(request)); + return doRunCommand(std::move(params)); } - auto result = _conn->runCommandWithTarget(std::move(request)).first; - return processResponseFLE1(processResponseFLE2(std::move(result), databaseName).first, - databaseName); + EncryptedDBClientBase::RunCommandReturn result(doRunCommand(std::move(params))); + return processResponseFLE1(processResponseFLE2(std::move(result), databaseName), databaseName); +} + +std::pair<rpc::UniqueReply, DBClientBase*> EncryptedDBClientBase::runCommandWithTarget( + OpMsgRequest request) { + EncryptedDBClientBase::RunCommandParams params(request); + auto result = handleEncryptionRequest(std::move(params)); + auto returnConn = stdx::get<DBClientBase*>(result.returnConn); + return {std::move(result.returnReply), returnConn}; +} + +std::pair<rpc::UniqueReply, std::shared_ptr<DBClientBase>> +EncryptedDBClientBase::runCommandWithTarget(OpMsgRequest request, + std::shared_ptr<DBClientBase> conn) { + EncryptedDBClientBase::RunCommandParams params(request, conn); + auto result = handleEncryptionRequest(std::move(params)); + auto returnConn = stdx::get<std::shared_ptr<DBClientBase>>(result.returnConn); + return {std::move(result.returnReply), returnConn}; } /** @@ -686,6 +715,10 @@ std::shared_ptr<SymmetricKey> EncryptedDBClientBase::getDataKey(const UUID& uuid return key; } +DBClientBase* EncryptedDBClientBase::getRawConnection() { + return _conn.get(); +} + SecureVector<uint8_t> EncryptedDBClientBase::getKeyMaterialFromDisk(const UUID& uuid) { NamespaceString fullNameNS = getCollectionNS(); FindCommandRequest findCmd{fullNameNS}; @@ -866,8 +899,16 @@ std::unique_ptr<DBClientBase> createEncryptedDBClientBase(std::unique_ptr<DBClie return std::move(base); } +DBClientBase* getNestedConnection(DBClientBase* conn) { + auto* encryptedConn = dynamic_cast<EncryptedDBClientBase*>(conn); + if (!encryptedConn) { + return nullptr; + } + return encryptedConn->getRawConnection(); +} + MONGO_INITIALIZER(setCallbacksForEncryptedDBClientBase)(InitializerContext*) { - mongo::mozjs::setEncryptedDBClientCallback(createEncryptedDBClientBase); + mongo::mozjs::setEncryptedDBClientCallbacks(createEncryptedDBClientBase, getNestedConnection); } } // namespace diff --git a/src/mongo/shell/encrypted_dbclient_base.h b/src/mongo/shell/encrypted_dbclient_base.h index 4c0a75434fb..f7a7608f70a 100644 --- a/src/mongo/shell/encrypted_dbclient_base.h +++ b/src/mongo/shell/encrypted_dbclient_base.h @@ -102,7 +102,11 @@ public: using DBClientBase::runCommandWithTarget; virtual std::pair<rpc::UniqueReply, DBClientBase*> runCommandWithTarget( - OpMsgRequest request) override; + OpMsgRequest request) final; + + std::pair<rpc::UniqueReply, std::shared_ptr<DBClientBase>> runCommandWithTarget( + OpMsgRequest request, std::shared_ptr<DBClientBase>) final; + std::string toString() const final; int getMinWireVersion() final; @@ -153,6 +157,8 @@ public: bool isMongos() const final; + DBClientBase* getRawConnection(); + #ifdef MONGO_CONFIG_SSL const SSLConfiguration* getSSLConfiguration() override; @@ -164,16 +170,54 @@ public: protected: BSONObj _decryptResponsePayload(BSONObj& reply, StringData databaseName, bool isFLE2); - std::pair<rpc::UniqueReply, DBClientBase*> processResponseFLE1(rpc::UniqueReply result, - StringData databaseName); + enum class RunCommandConnectionType { rawPtr, sharedPtr }; + + struct RunCommandParams { + OpMsgRequest request; + std::shared_ptr<DBClientBase> conn; + RunCommandConnectionType type; + + RunCommandParams(OpMsgRequest request) + : request(std::move(request)), type(RunCommandConnectionType::rawPtr){}; + + RunCommandParams(OpMsgRequest request, std::shared_ptr<DBClientBase> base) + : request(std::move(request)), conn(base), type(RunCommandConnectionType::sharedPtr){}; + + RunCommandParams(OpMsgRequest request, RunCommandParams params) + : request(std::move(request)), type(params.type) { + if (type == RunCommandConnectionType::sharedPtr) { + conn = params.conn; + }; + }; + }; + + using RunCommandReturnConn = stdx::variant<DBClientBase*, std::shared_ptr<DBClientBase>>; + + struct RunCommandReturn { + rpc::UniqueReply returnReply; + RunCommandReturnConn returnConn; + + RunCommandReturn(std::pair<rpc::UniqueReply, DBClientBase*> pair) + : returnReply(std::move(std::get<0>(pair))), returnConn(std::get<1>(pair)) {} + + RunCommandReturn(std::pair<rpc::UniqueReply, std::shared_ptr<DBClientBase>> pair) + : returnReply(std::move(std::get<0>(pair))), returnConn(std::get<1>(pair)) {} + + RunCommandReturn(rpc::UniqueReply reply, RunCommandReturn& result) + : returnReply(std::move(reply)), returnConn(result.returnConn) {} + }; + + RunCommandReturn doRunCommand(RunCommandParams params); + + virtual RunCommandReturn handleEncryptionRequest(RunCommandParams params); - std::pair<rpc::UniqueReply, DBClientBase*> processResponseFLE2(rpc::UniqueReply result, - StringData databaseName); + RunCommandReturn processResponseFLE1(RunCommandReturn result, StringData databaseName); - std::pair<rpc::UniqueReply, DBClientBase*> prepareReply(rpc::UniqueReply result, - StringData databaseName, - BSONObj decryptedDoc); + RunCommandReturn processResponseFLE2(RunCommandReturn result, StringData DatabaseName); + RunCommandReturn prepareReply(RunCommandReturn result, + StringData databaseName, + BSONObj decryptedDoc); BSONObj encryptDecryptCommand(const BSONObj& object, bool encrypt, StringData databaseName); diff --git a/src/mongo/shell/keyvault.js b/src/mongo/shell/keyvault.js index c913d81a5b1..ee8f2ddf111 100644 --- a/src/mongo/shell/keyvault.js +++ b/src/mongo/shell/keyvault.js @@ -6,13 +6,37 @@ Mongo.prototype.getKeyVault = function() { }; class KeyVault { + _runCommand(client, func, args) { + let numRetries = 3; + do { + try { + const result = func.apply(client, args); + return result; + } catch (e) { + numRetries--; + if (!isNetworkError(e) || numRetries == 0) { + jsTest.log("KeyVault: We have exceeded the number of retries. Throwing."); + throw e; + } + + const res = this.mongo.getDB('admin')._helloOrLegacyHello(); + if (!res) { + jsTest.log("KeyVault: We do not have a connection to the database. Throwing."); + throw e; + } + this.keyColl = this.mongo.getDataKeyCollection(); + } + } while (true); + } + constructor(mongo) { this.mongo = mongo; - var collection = mongo.getDataKeyCollection(); + var collection = this._runCommand(this.mongo, this.mongo.getDataKeyCollection, {}); this.keyColl = collection; - this.keyColl.createIndex( + this._runCommand(this.keyColl, this.keyColl.createIndex, [ {keyAltNames: 1}, - {unique: true, partialFilterExpression: {keyAltNames: {$exists: true}}}); + {unique: true, partialFilterExpression: {keyAltNames: {$exists: true}}} + ]); } createKey(kmsProvider, param2 = undefined, param3 = undefined) { @@ -35,7 +59,8 @@ class KeyVault { return "TypeError: customer master key must be of String type."; } - var masterKeyAndMaterial = this.mongo.generateDataKey(kmsProvider, customerMasterKey); + var masterKeyAndMaterial = this._runCommand( + this.mongo, this.mongo.generateDataKey, [kmsProvider, customerMasterKey]); var masterKey = masterKeyAndMaterial.masterKey; var current = ISODate(); @@ -66,24 +91,24 @@ class KeyVault { doc.keyAltNames = keyAltNames; } - this.keyColl.insert(doc); + this._runCommand(this.keyColl, this.keyColl.insert, [doc]); return uuid; } getKey(keyId) { - return this.keyColl.find({"_id": keyId}); + return this._runCommand(this.keyColl, this.keyColl.find, [{"_id": keyId}]); } getKeyByAltName(keyAltName) { - return this.keyColl.find({"keyAltNames": keyAltName}); + return this._runCommand(this.keyColl, this.keyColl.find, [{"keyAltNames": keyAltName}]); } deleteKey(keyId) { - return this.keyColl.deleteOne({"_id": keyId}); + return this._runCommand(this.keyColl, this.keyColl.deleteOne, [{"_id": keyId}]); } getKeys() { - return this.keyColl.find(); + return this._runCommand(this.keyColl, this.keyColl.find, []); } addKeyAlternateName(keyId, keyAltName) { @@ -92,27 +117,31 @@ class KeyVault { if (typeof keyAltName === "object") { return "TypeError: key alternate name cannot be object or array type."; } - return this.keyColl.findAndModify({ - query: {"_id": keyId}, - update: {$push: {"keyAltNames": keyAltName}, $currentDate: {"updateDate": true}}, - }); + return this._runCommand( + this.keyColl, this.keyColl.findAndModify, [{ + query: {"_id": keyId}, + update: {$push: {"keyAltNames": keyAltName}, $currentDate: {"updateDate": true}}, + }]); } removeKeyAlternateName(keyId, keyAltName) { if (typeof keyAltName === "object") { return "TypeError: key alternate name cannot be object or array type."; } - const ret = this.keyColl.findAndModify({ - query: {"_id": keyId}, - update: {$pull: {"keyAltNames": keyAltName}, $currentDate: {"updateDate": true}} - }); + + const ret = this._runCommand( + this.keyColl, this.keyColl.findAndModify, [{ + query: {"_id": keyId}, + update: {$pull: {"keyAltNames": keyAltName}, $currentDate: {"updateDate": true}} + }]); if (ret != null && ret.keyAltNames.length === 1 && ret.keyAltNames[0] === keyAltName) { // Remove the empty array to prevent duplicate key violations - return this.keyColl.findAndModify({ - query: {"_id": keyId, "keyAltNames": undefined}, - update: {$unset: {"keyAltNames": ""}, $currentDate: {"updateDate": true}} - }); + return this._runCommand( + this.keyColl, this.keyColl.findAndModify, [{ + query: {"_id": keyId, "keyAltNames": undefined}, + update: {$unset: {"keyAltNames": ""}, $currentDate: {"updateDate": true}} + }]); } return ret; } diff --git a/src/mongo/shell/linenoise.cpp b/src/mongo/shell/linenoise.cpp index 679578d133b..d62082aa089 100644 --- a/src/mongo/shell/linenoise.cpp +++ b/src/mongo/shell/linenoise.cpp @@ -119,6 +119,7 @@ #include <string> #include <vector> +#include "mongo/base/data_view.h" #include "mongo/util/errno_util.h" using std::string; @@ -352,7 +353,7 @@ public: } Utf32String killedText(text, textLen); if (lastAction == actionKill && size > 0) { - int slot = indexToSlot[0]; + int slot = mongo::ConstDataView(&indexToSlot[0]).read<uint8_t>(); int currentLen = theRing[slot].length(); int resultLen = currentLen + textLen; Utf32String temp(resultLen + 1); @@ -375,7 +376,7 @@ public: size++; theRing.push_back(killedText); } else { - int slot = indexToSlot[capacity - 1]; + int slot = mongo::ConstDataView(&indexToSlot[capacity - 1]).read<uint8_t>(); theRing[slot] = killedText; memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1); indexToSlot[0] = slot; diff --git a/src/mongo/shell/mongo_main.cpp b/src/mongo/shell/mongo_main.cpp index b6cac96d526..6d3b5f37244 100644 --- a/src/mongo/shell/mongo_main.cpp +++ b/src/mongo/shell/mongo_main.cpp @@ -116,14 +116,14 @@ const std::string kDefaultMongoURL = "mongodb://"s + kDefaultMongoHost + ":"s + // level. The server is responsible for rejecting usages of new features if its // featureCompatibilityVersion is lower. MONGO_INITIALIZER_WITH_PREREQUISITES(SetFeatureCompatibilityVersionLatest, - ("EndStartupOptionSetup")) + ("EndStartupOptionStorage")) // (Generic FCV reference): This FCV reference should exist across LTS binary versions. (InitializerContext* context) { mongo::serverGlobalParams.mutableFeatureCompatibility.setVersion( multiversion::GenericFCV::kLatest); } -MONGO_INITIALIZER_WITH_PREREQUISITES(WireSpec, ("EndStartupOptionSetup"))(InitializerContext*) { +MONGO_INITIALIZER_WITH_PREREQUISITES(WireSpec, ("EndStartupOptionHandling"))(InitializerContext*) { WireSpec::instance().initialize(WireSpec::Specification{}); } @@ -1005,12 +1005,6 @@ int mongo_main(int argc, char* argv[]) { true, false); - scope->exec("shellHelper( 'show', 'freeMonitoring' )", - "(freeMonitoring)", - false, - true, - false); - scope->exec("shellHelper( 'show', 'automationNotices' )", "(automationnotices)", false, diff --git a/src/mongo/shell/replsettest.js b/src/mongo/shell/replsettest.js index 827691f822d..1bd4b60d817 100644 --- a/src/mongo/shell/replsettest.js +++ b/src/mongo/shell/replsettest.js @@ -656,6 +656,9 @@ var ReplSetTest = function(opts) { // replica set nodes and return without waiting to connect to any of them. const skipWaitingForAllConnections = (options && options.waitForConnect === false); + // Keep a copy of these options + self.startSetOptions = options; + // Start up without waiting for connections. this.startSetAsync(options, restart); @@ -730,8 +733,8 @@ var ReplSetTest = function(opts) { /** * Blocks until the secondary nodes have completed recovery and their roles are known. Blocks on - * all secondary nodes or just 'secondaries', if specified. Waits for all 'newlyAdded' fields to - * be removed by default. + * all secondary nodes or just 'secondaries', if specified. Does not wait for all 'newlyAdded' + * fields to be removed by default. */ this.awaitSecondaryNodes = function( timeout, secondaries, retryIntervalMS, waitForNewlyAddedRemoval) { @@ -1361,6 +1364,28 @@ var ReplSetTest = function(opts) { cmd[cmdKey] = config; + // If this ReplSet is started using this.startSet and binVersions (ie: + // rst.startSet({binVersion: [...]}) we need to make sure the binVersion combination is + // valid. + if (typeof (this.startSetOptions) === "object" && + this.startSetOptions.hasOwnProperty("binVersion") && + typeof (this.startSetOptions.binVersion) === "object") { + let lastLTSSpecified = false; + let lastContinuousSpecified = false; + this.startSetOptions.binVersion.forEach(function(binVersion, _) { + if (lastLTSSpecified === false) { + lastLTSSpecified = MongoRunner.areBinVersionsTheSame(binVersion, lastLTSFCV); + } + if ((lastContinuousSpecified === false) && (lastLTSFCV !== lastContinuousFCV)) { + lastContinuousSpecified = + MongoRunner.areBinVersionsTheSame(binVersion, lastContinuousFCV); + } + }); + if (lastLTSSpecified && lastContinuousSpecified) { + throw new Error("Can only specify one of 'last-lts' and 'last-continuous' " + + "in binVersion, not both."); + } + } // Initiating a replica set with a single node will use "latest" FCV. This will // cause IncompatibleServerVersion errors if additional "last-lts"/"last-continuous" binary // version nodes are subsequently added to the set, since such nodes cannot set their FCV to @@ -1372,11 +1397,15 @@ var ReplSetTest = function(opts) { Object.keys(this.nodeOptions).forEach(function(key, index) { let val = self.nodeOptions[key]; if (typeof (val) === "object" && val.hasOwnProperty("binVersion")) { - lastLTSBinVersionWasSpecifiedForSomeNode = - MongoRunner.areBinVersionsTheSame(val.binVersion, lastLTSFCV); - lastContinuousBinVersionWasSpecifiedForSomeNode = - (lastLTSFCV !== lastContinuousFCV) && - MongoRunner.areBinVersionsTheSame(val.binVersion, lastContinuousFCV); + if (lastLTSBinVersionWasSpecifiedForSomeNode === false) { + lastLTSBinVersionWasSpecifiedForSomeNode = + MongoRunner.areBinVersionsTheSame(val.binVersion, lastLTSFCV); + } + if ((lastContinuousBinVersionWasSpecifiedForSomeNode === false) && + (lastLTSFCV !== lastContinuousFCV)) { + lastContinuousBinVersionWasSpecifiedForSomeNode = + MongoRunner.areBinVersionsTheSame(val.binVersion, lastContinuousFCV); + } explicitBinVersionWasSpecifiedForSomeNode = true; } }); diff --git a/src/mongo/shell/servers.js b/src/mongo/shell/servers.js index 215e7ce3d37..1040b3c991d 100644 --- a/src/mongo/shell/servers.js +++ b/src/mongo/shell/servers.js @@ -1380,6 +1380,19 @@ function appendSetParameterArgs(argArray) { argArray.push(...['--setParameter', "logComponentVerbosity=" + logVerbosityParam]); } + if (programMajorMinorVersion >= 530 && + !argArrayContainsSetParameterValue("backtraceLogFile=")) { + let randomName = ""; + let randomStrLen = 20; + const chars = "qwertyuiopasdfghjklzxcvbnm1234567890"; + for (let i = 0; i <= randomStrLen; i++) { + randomName += chars[((Math.random() * 1000) % chars.length) ^ 0]; + } + const backtraceLogFilePath = + MongoRunner.dataDir + "/" + randomName + Date.now() + ".stacktrace"; + argArray.push(...["--setParameter", "backtraceLogFile=" + backtraceLogFilePath]); + } + // When launching a 5.0 mongod, if we're mentioning the // `storeFindAndModifyImagesInSideCollection` setParameter and the corresponding feature // flag is not set, add it for good measure. diff --git a/src/mongo/shell/shardingtest.js b/src/mongo/shell/shardingtest.js index 60f6e83fac7..e8796a9a222 100644 --- a/src/mongo/shell/shardingtest.js +++ b/src/mongo/shell/shardingtest.js @@ -10,6 +10,7 @@ * * { * 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 * @@ -404,6 +405,24 @@ 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) @@ -575,53 +594,12 @@ var ShardingTest = function(params) { }; /** - * 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); + * 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); }; this.getShard = function(coll, query, includeEmpty) { @@ -1014,6 +992,13 @@ var ShardingTest = function(params) { }; /** + * Waits for all operations to fully replicate on all shards. + */ + this.awaitReplicationOnShards = function() { + this._rs.forEach(replSet => replSet.test.awaitReplication()); + }; + + /** * 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. */ @@ -1186,537 +1171,551 @@ var ShardingTest = function(params) { randomSeedAlreadySet = true; } - // - // 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}), - }; + 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}), + }; - 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.rs || otherParams["rs" + i]) { + if (otherParams.rs) { + rsDefaults = Object.merge(rsDefaults, otherParams.rs); } - // 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" + 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.shardOptions && otherParams.shardOptions.binVersion) { - otherParams.shardOptions.binVersion = - MongoRunner.versionIterator(otherParams.shardOptions.binVersion); + if (otherParams.shardOptions && otherParams.shardOptions.binVersion) { + otherParams.shardOptions.binVersion = + MongoRunner.versionIterator(otherParams.shardOptions.binVersion); + } + + rsDefaults = Object.merge(rsDefaults, otherParams["d" + i]); + rsDefaults = Object.merge(rsDefaults, otherParams.shardOptions); } - rsDefaults = Object.merge(rsDefaults, otherParams["d" + i]); - rsDefaults = Object.merge(rsDefaults, otherParams.shardOptions); - } + rsDefaults.setParameter = rsDefaults.setParameter || {}; + rsDefaults.setParameter.migrationLockAcquisitionMaxWaitMS = + otherParams.migrationLockAcquisitionMaxWaitMS; - rsDefaults.setParameter = rsDefaults.setParameter || {}; - rsDefaults.setParameter.migrationLockAcquisitionMaxWaitMS = - otherParams.migrationLockAcquisitionMaxWaitMS; + var rsSettings = rsDefaults.settings; + delete rsDefaults.settings; + + // 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); - // 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; + // 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()}; } - delete rsDefaults.nodes; - var protocolVersion = rsDefaults.protocolVersion; - delete rsDefaults.protocolVersion; + // + // Start up the config server replica set. + // - var rs = new ReplSetTest({ - name: setName, - nodes: numReplicas, - host: hostName, + var rstOptions = { useHostName: otherParams.useHostname, + host: hostName, useBridge: otherParams.useBridge, bridgeOptions: otherParams.bridgeOptions, keyFile: this.keyFile, - protocolVersion: protocolVersion, waitForKeys: false, - settings: rsSettings, + name: testName + "-configRS", seedRandomNumberGenerator: !randomSeedAlreadySet, - }); + isConfigServer: true, + }; - print("ShardingTest starting replica set for shard: " + setName); + // 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", + }; - // 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()}; - } + if (otherParams.configOptions && otherParams.configOptions.binVersion) { + otherParams.configOptions.binVersion = + MongoRunner.versionIterator(otherParams.configOptions.binVersion); + } - // - // 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, - }; + startOptions = Object.merge(startOptions, otherParams.configOptions); + rstOptions = Object.merge(rstOptions, otherParams.configReplSetTestOptions); - // 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", - }; + var nodeOptions = []; + for (var i = 0; i < numConfigs; ++i) { + nodeOptions.push(otherParams["c" + i] || {}); + } - if (otherParams.configOptions && otherParams.configOptions.binVersion) { - otherParams.configOptions.binVersion = - MongoRunner.versionIterator(otherParams.configOptions.binVersion); - } + rstOptions.nodes = nodeOptions; - startOptions = Object.merge(startOptions, otherParams.configOptions); - rstOptions = Object.merge(rstOptions, otherParams.configReplSetTestOptions); + // 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); - var nodeOptions = []; - for (var i = 0; i < numConfigs; ++i) { - nodeOptions.push(otherParams["c" + i] || {}); - } + // + // 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; + } - rstOptions.nodes = nodeOptions; + if (rst === this.configRS) { + rstConfig.configsvr = true; + rstConfig.writeConcernMajorityJournalDefault = true; + } - // 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); + 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, + }; + }); - // - // 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(); - } + const initiateReplicaSet = (rst, rstConfig) => { + rst.initiateWithAnyNodeAsPrimary(rstConfig); - // - // 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, + // 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(); }; - }); - - const initiateReplicaSet = (rst, rstConfig) => { - rst.initiateWithAnyNodeAsPrimary(rstConfig); - // 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(); - }; - - const isParallelSupported = (() => { - if (!tryLoadParallelTester()) { - return false; - } - - 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. + const isParallelSupported = (() => { + if (!tryLoadParallelTester()) { return false; } - for (let n of Object.keys(rst.nodeOptions)) { - const nodeOptions = rst.nodeOptions[n]; - if (nodeOptions && nodeOptions.clusterAuthMode === "x509") { + 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; } - } - } - - return true; - })(); - 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, - }; + for (let n of Object.keys(rst.nodeOptions)) { + const nodeOptions = rst.nodeOptions[n]; + if (nodeOptions && nodeOptions.clusterAuthMode === "x509") { + return false; } - }, 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(); - }); - 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); + return true; + })(); + + 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(); + }); + + 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; + for (let i = 0; i < numShards; i++) { + let rs = this._rs[i].test; - this["rs" + i] = rs; - this._rsObjects[i] = rs; + this["rs" + i] = rs; + this._rsObjects[i] = rs; - this._connections.push(null); + this._connections.push(null); - let rsConn = new Mongo(rs.getURL()); - rsConn.name = rs.getURL(); + let rsConn = new Mongo(rs.getURL()); + rsConn.name = rs.getURL(); - this._connections[i] = rsConn; - this["shard" + i] = rsConn; - rsConn.rs = rs; - } + this._connections[i] = rsConn; + this["shard" + i] = rsConn; + rsConn.rs = rs; + } - // 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, - }; + // Wait for master to be elected before starting mongos + this.configRS.awaitNodesAgreeOnPrimary(); + var csrsPrimary = this.configRS.getPrimary(); - if (otherParams.mongosOptions && otherParams.mongosOptions.binVersion) { - otherParams.mongosOptions.binVersion = - MongoRunner.versionIterator(otherParams.mongosOptions.binVersion); - } + 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."); - options = Object.merge(options, otherParams.mongosOptions); - options = Object.merge(options, otherParams["s" + i]); + // 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, + }; - // 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 (otherParams.mongosOptions && otherParams.mongosOptions.binVersion) { + otherParams.mongosOptions.binVersion = + MongoRunner.versionIterator(otherParams.mongosOptions.binVersion); + } - options.port = options.port || _allocatePortForMongos(); + options = Object.merge(options, otherParams.mongosOptions); + options = Object.merge(options, otherParams["s" + i]); - mongosOptions.push(options); - } + // 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; - 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.port = options.port || _allocatePortForMongos(); - // 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(); + mongosOptions.push(options); } - if (this.keyFile) { - authutil.asCluster(this.configRS.nodes, this.keyFile, setFeatureCompatibilityVersion); - } else { - setFeatureCompatibilityVersion(); + 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})); + + // 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(); + } + + if (this.keyFile) { + authutil.asCluster( + this.configRS.nodes, this.keyFile, setFeatureCompatibilityVersion); + } else { + setFeatureCompatibilityVersion(); + } } - } - // 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}})); + // 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}})); + + configRS.awaitLastOpCommitted(); + } - configRS.awaitLastOpCommitted(); + if (this.keyFile) { + authutil.asCluster(csrsPrimary, this.keyFile, setChunkSize); + } else { + setChunkSize(); + } } - if (this.keyFile) { - authutil.asCluster(csrsPrimary, this.keyFile, setChunkSize); - } else { - setChunkSize(); + 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; } - } - 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; - } + printjson('Config servers: ' + this._configDB); - printjson('Config servers: ' + this._configDB); + print("ShardingTest " + this._testName + " :\n" + + tojson({config: this._configDB, shards: this._connections})); - print("ShardingTest " + this._testName + " :\n" + - tojson({config: this._configDB, shards: this._connections})); + this._mongos = []; - this._mongos = []; + // Start the MongoS servers + for (var i = 0; i < numMongos; i++) { + const options = mongosOptions[i]; + options.configdb = this._configDB; - // Start the MongoS servers - for (var i = 0; i < numMongos; i++) { - const options = mongosOptions[i]; - options.configdb = this._configDB; + 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 (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, - }); + var bridge = new MongoBridge(bridgeOptions); + } - var bridge = new MongoBridge(bridgeOptions); - } + var conn = MongoRunner.runMongos(options); + if (!conn) { + throw new Error("Failed to start mongos " + i); + } - var conn = MongoRunner.runMongos(options); - if (!conn) { - throw new Error("Failed to start mongos " + i); - } + if (otherParams.causallyConsistent) { + conn.setCausalConsistency(true); + } - if (otherParams.causallyConsistent) { - conn.setCausalConsistency(true); - } + if (otherParams.useBridge) { + bridge.connectToBridge(); + this._mongos.push(bridge); + unbridgedMongos.push(conn); + } else { + this._mongos.push(conn); + } - if (otherParams.useBridge) { - bridge.connectToBridge(); - this._mongos.push(bridge); - unbridgedMongos.push(conn); - } else { - this._mongos.push(conn); - } + if (i === 0) { + this.s = this._mongos[i]; + this.admin = this._mongos[i].getDB('admin'); + this.config = this._mongos[i].getDB('config'); + } - if (i === 0) { - this.s = this._mongos[i]; - this.admin = this._mongos[i].getDB('admin'); - this.config = this._mongos[i].getDB('config'); + this["s" + i] = this._mongos[i]; } - this["s" + i] = this._mongos[i]; - } + _extendWithShMethods(); - _extendWithShMethods(); + // 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(); + } - // 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; - try { - if (!otherParams.manualAddShard) { - var testName = this._testName; - var admin = this.admin; + this._connections.forEach(function(z) { + var n = z.name || z.host || z; - this._connections.forEach(function(z) { - var n = z.name || z.host || z; + print("ShardingTest " + testName + " going to add shard : " + n); - 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; + } + + // 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. + this.configRS.awaitLastOpCommitted(); - var result = assert.commandWorked(admin.runCommand({addshard: n}), - "Failed to add shard " + n); - z.shardName = result.shardAdded; - }); + if (jsTestOptions().keyFile) { + jsTest.authenticateNodes(this._mongos); } - } 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. - this.configRS.awaitLastOpCommitted(); - 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"})); + }; - // 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"})); + 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); + } }; - 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); - } - }; - - 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; + 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); + } - const rs = this._rs[i].test; - flushRT(rs.getPrimary(), keyFileLocal); + self.waitForShardingInitialized(); } - - self.waitForShardingInitialized(); + } catch (e) { + // this was expected to fail, so clean up appropriately + if (params.shouldFailInit === true) { + this.stopOnFail(); + } + throw e; } + // 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 diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index cb82f223f9b..abdbb179bfa 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -521,14 +521,22 @@ BSONObj numberDecimalsAlmostEqual(const BSONObj& input, void*) { auto ten = Decimal128(10); auto exponent = a.toAbs().logarithm(ten).round(); - // Return early if arguments are not the same order of magnitude. - if (exponent != b.toAbs().logarithm(ten).round()) { - return BSON("" << false); - } + // Early exit for zero, infinity and NaN cases. + if ((a.isZero() && b.isZero()) || (a.isNaN() && b.isNaN()) || + (a.isInfinite() && b.isInfinite() && (a.isNegative() == b.isNegative()))) { + return BSON("" << true /* isErrorAcceptable */); + } else if (!a.isZero() && !b.isZero()) { + // Return early if arguments are not the same order of magnitude. + if (exponent != b.toAbs().logarithm(ten).round()) { + return BSON("" << false); + } - // Put the whole number behind the decimal point. - a = a.divide(ten.power(exponent)); - b = b.divide(ten.power(exponent)); + // Put the whole number behind the decimal point. + if (!exponent.isZero()) { + a = a.divide(ten.power(exponent)); + b = b.divide(ten.power(exponent)); + } + } auto places = third.numberDecimal(); auto isErrorAcceptable = a.subtract(b) diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js index ebfa03f7268..da2c29fa493 100644 --- a/src/mongo/shell/utils.js +++ b/src/mongo/shell/utils.js @@ -1130,52 +1130,6 @@ shellHelper.show = function(what) { } } - if (what == "freeMonitoring") { - var dbDeclared, ex; - try { - // !!db essentially casts db to a boolean - // Will throw a reference exception if db hasn't been declared. - dbDeclared = !!db; - } catch (ex) { - dbDeclared = false; - } - - if (dbDeclared) { - const freemonStatus = db.adminCommand({getFreeMonitoringStatus: 1}); - - if (freemonStatus.ok) { - if (freemonStatus.state == 'enabled' && - freemonStatus.hasOwnProperty('userReminder')) { - print("---"); - print(freemonStatus.userReminder); - print("---"); - } else if (freemonStatus.state === 'undecided') { - print( - "---\n" + messageIndent + - "Enable MongoDB's free cloud-based monitoring service, which will then receive and display\n" + - messageIndent + - "metrics about your deployment (disk utilization, CPU, operation statistics, etc).\n" + - "\n" + messageIndent + - "The monitoring data will be available on a MongoDB website with a unique URL accessible to you\n" + - messageIndent + - "and anyone you share the URL with. MongoDB may use this information to make product\n" + - messageIndent + - "improvements and to suggest MongoDB products and deployment options to you.\n" + - "\n" + messageIndent + - "To enable free monitoring, run the following command: db.enableFreeMonitoring()\n" + - messageIndent + - "To permanently disable this reminder, run the following command: db.disableFreeMonitoring()\n" + - "---\n"); - } - } - - return ""; - } else { - print("Cannot show freeMonitoring, \"db\" is not set"); - return ""; - } - } - if (what == "nonGenuineMongoDBCheck") { let matchesKnownImposterSignature = false; diff --git a/src/mongo/shell/utils_sh.js b/src/mongo/shell/utils_sh.js index b4bd7175096..e086bbf8f46 100644 --- a/src/mongo/shell/utils_sh.js +++ b/src/mongo/shell/utils_sh.js @@ -96,6 +96,9 @@ sh.help = function() { "returns wheter the specified collection is balanced or the balancer needs to take more actions on it"); print("\tsh.configureCollectionBalancing(fullName, params) " + "configure balancing settings for a specific collection"); + print("\tsh.awaitCollectionBalance(coll) waits for a collection to be balanced"); + print( + "\tsh.verifyCollectionIsBalanced(coll) verifies that a collection is well balanced by checking the actual data size on each shard"); }; sh.status = function(verbose, configDB) { @@ -255,18 +258,28 @@ sh.waitForPingChange = function(activePings, timeout, interval) { return remainingPings; }; -sh.waitForBalancer = function(wait, timeout, interval) { - if (typeof (wait) === 'undefined') { - wait = false; - } +/** + * 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. + */ +sh.awaitBalancerRound = function(timeout, interval) { + timeout = timeout || 60000; + var initialStatus = sh._getBalancerStatus(); - if (!initialStatus.inBalancerRound && !wait) { - return; - } var currentStatus; assert.soon(function() { currentStatus = sh._getBalancerStatus(); - return (currentStatus.numBalancerRounds - initialStatus.numBalancerRounds) != 0; + assert.eq(currentStatus.mode, 'full', "Balancer is disabled"); + if (!friendlyEqual(currentStatus.term, initialStatus.term)) { + // A new primary of the csrs has been elected + initialStatus = currentStatus; + return false; + } + assert.gte(currentStatus.numBalancerRounds, + initialStatus.numBalancerRounds, + 'Number of balancer rounds moved back in time unexpectedly. Current status: ' + + tojson(currentStatus) + ', initial status: ' + tojson(initialStatus)); + return currentStatus.numBalancerRounds > initialStatus.numBalancerRounds; }, 'Latest balancer status: ' + tojson(currentStatus), timeout, interval); }; @@ -304,6 +317,118 @@ sh.enableBalancing = function(coll) { {writeConcern: {w: 'majority', wtimeout: 60000}})); }; +sh.awaitCollectionBalance = function(coll, timeout, interval) { + if (coll === undefined) { + throw Error("Must specify collection"); + } + timeout = timeout || 60000; + interval = interval || 200; + + const ns = coll.getFullName(); + const orphanDocsPipeline = [ + {'$collStats': {'storageStats': {}}}, + {'$project': {'shard': true, 'storageStats': {'numOrphanDocs': true}}}, + {'$group': {'_id': null, 'totalNumOrphanDocs': {'$sum': '$storageStats.numOrphanDocs'}}} + ]; + + var oldDb = (typeof (db) === 'undefined' ? undefined : db); + try { + db = coll.getDB(); + + assert.soon( + function() { + assert.soon(function() { + return assert + .commandWorked(sh._adminCommand({balancerCollectionStatus: ns}, true)) + .balancerCompliant; + }, 'Timed out waiting for the collection to be balanced', timeout, interval); + + // (SERVER-67301) Wait for orphans counter to be 0 to account for potential stale + // orphans count + sh.disableBalancing(coll); + assert.soon(function() { + return coll.aggregate(orphanDocsPipeline).toArray()[0].totalNumOrphanDocs === 0; + }, 'Timed out waiting for orphans counter to be 0', timeout, interval); + sh.enableBalancing(coll); + + // (SERVER-70602) Wait for some balancing rounds to avoid balancerCollectionStatus + // reporting balancerCompliant too early + for (let i = 0; i < 3; ++i) { + sh.awaitBalancerRound(timeout, interval); + } + + return assert.commandWorked(sh._adminCommand({balancerCollectionStatus: ns}, true)) + .balancerCompliant; + }, + 'Timed out waiting for collection to be balanced and orphans counter to be 0', + timeout, + interval); + } finally { + db = oldDb; + } +}; + +/** + * Verifies if given collection is properly balanced according to the data size aware balancing + * policy + */ +sh.verifyCollectionIsBalanced = function(coll) { + if (coll === undefined) { + throw Error("Must specify collection"); + } + + var oldDb = db; + try { + db = coll.getDB(); + + const configDB = sh._getConfigDB(); + const ns = coll.getFullName(); + const collection = configDB.collections.findOne({_id: ns}); + + let collSizeOnShards = []; + let shards = []; + const collStatsPipeline = [ + {'$collStats': {'storageStats': {}}}, + { + '$project': { + 'shard': true, + 'storageStats': + {'count': true, 'size': true, 'avgObjSize': true, 'numOrphanDocs': true} + } + }, + {'$sort': {'shard': 1}} + ]; + + let kChunkSize = 1024 * 1024 * + assert.commandWorked(sh._adminCommand({balancerCollectionStatus: ns})).chunkSize; + // TODO SERVER-67898 delete kChunkSize overwrite after completing the ticket + if (kChunkSize == 0) { + kChunkSize = collection.maxChunkSizeBytes; + } + + // Get coll size per shard + const storageStats = coll.aggregate(collStatsPipeline).toArray(); + coll.aggregate(collStatsPipeline).forEach((shardStats) => { + shards.push(shardStats['shard']); + const collSize = (shardStats['storageStats']['count'] - + shardStats['storageStats']['numOrphanDocs']) * + shardStats['storageStats']['avgObjSize']; + collSizeOnShards.push(collSize); + }); + + let errorMsg = "Collection not balanced. collection= " + tojson(collection) + + ", shards= " + tojson(shards) + ", collSizeOnShards=" + tojson(collSizeOnShards) + + ", storageStats=" + tojson(storageStats) + ", kChunkSize=" + tojson(kChunkSize); + + assert.lte((Math.max(...collSizeOnShards) - Math.min(...collSizeOnShards)), + 3 * kChunkSize, + errorMsg); + + } finally { + db = oldDb; + } +}; + /* * Can call _lastMigration( coll ), _lastMigration( db ), _lastMigration( st ), _lastMigration( * mongos ) |
