summaryrefslogtreecommitdiff
path: root/src/mongo/shell
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/shell')
-rw-r--r--src/mongo/shell/assert.js3
-rw-r--r--src/mongo/shell/db.js51
-rw-r--r--src/mongo/shell/encrypted_dbclient_base.cpp77
-rw-r--r--src/mongo/shell/encrypted_dbclient_base.h60
-rw-r--r--src/mongo/shell/keyvault.js71
-rw-r--r--src/mongo/shell/linenoise.cpp5
-rw-r--r--src/mongo/shell/mongo_main.cpp10
-rw-r--r--src/mongo/shell/replsettest.js43
-rw-r--r--src/mongo/shell/servers.js13
-rw-r--r--src/mongo/shell/shardingtest.js977
-rw-r--r--src/mongo/shell/shell_utils.cpp22
-rw-r--r--src/mongo/shell/utils.js46
-rw-r--r--src/mongo/shell/utils_sh.js141
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 )