summaryrefslogtreecommitdiff
path: root/jstests/libs
diff options
context:
space:
mode:
authorMax Hirschhorn <max.hirschhorn@mongodb.com>2018-01-30 19:45:42 -0500
committerMax Hirschhorn <max.hirschhorn@mongodb.com>2018-01-30 19:45:42 -0500
commit35b5b72146ca570b5c6fed8aaa7e891edf7d6a78 (patch)
tree982b7782084039d440c75911b51ac6568569faec /jstests/libs
parent784e55320f72ab9b9ec8b4f766d4be0c1b5e4a5b (diff)
downloadmongo-35b5b72146ca570b5c6fed8aaa7e891edf7d6a78.tar.gz
SERVER-32522 Clean up {read,write}Concern and readPreference overrides.
Introduces OverrideHelpers object with convenience methods for inspecting certain aggregation and map-reduce commands, as well as overriding startParallelShell(), Mongo.prototype.runCommand(), and Mongo.prototype.runCommandWithMetadata(). Also removes a number of tests that were incorrectly blacklisted from the read_concern_majority_passthrough.yml and read_concern_linearizable_passthrough.yml test suites.
Diffstat (limited to 'jstests/libs')
-rw-r--r--jstests/libs/override_methods/auto_retry_on_network_error.js17
-rw-r--r--jstests/libs/override_methods/enable_causal_consistency.js17
-rw-r--r--jstests/libs/override_methods/enable_sessions.js17
-rw-r--r--jstests/libs/override_methods/override_helpers.js109
-rw-r--r--jstests/libs/override_methods/retry_writes_at_least_once.js17
-rw-r--r--jstests/libs/override_methods/set_read_and_write_concerns.js354
-rw-r--r--jstests/libs/override_methods/set_read_preference_secondary.js87
7 files changed, 357 insertions, 261 deletions
diff --git a/jstests/libs/override_methods/auto_retry_on_network_error.js b/jstests/libs/override_methods/auto_retry_on_network_error.js
index f7f2138da28..14aa83a0a7f 100644
--- a/jstests/libs/override_methods/auto_retry_on_network_error.js
+++ b/jstests/libs/override_methods/auto_retry_on_network_error.js
@@ -11,6 +11,7 @@
(function() {
"use strict";
+ load("jstests/libs/override_methods/override_helpers.js");
load("jstests/libs/retryable_writes_util.js");
const kMaxNumRetries = 3;
@@ -350,20 +351,8 @@
} while (numRetries >= 0);
}
- const startParallelShellOriginal = startParallelShell;
-
- startParallelShell = function(jsCode, port, noConnect) {
- let newCode;
- const overridesFile = "jstests/libs/override_methods/auto_retry_on_network_error.js";
- if (typeof(jsCode) === "function") {
- // Load the override file and immediately invoke the supplied function.
- newCode = `load("${overridesFile}"); (${jsCode})();`;
- } else {
- newCode = `load("${overridesFile}"); ${jsCode};`;
- }
-
- return startParallelShellOriginal(newCode, port, noConnect);
- };
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/auto_retry_on_network_error.js");
const connectOriginal = connect;
diff --git a/jstests/libs/override_methods/enable_causal_consistency.js b/jstests/libs/override_methods/enable_causal_consistency.js
index f932304df7a..9e8a4bdde59 100644
--- a/jstests/libs/override_methods/enable_causal_consistency.js
+++ b/jstests/libs/override_methods/enable_causal_consistency.js
@@ -4,20 +4,11 @@
(function() {
"use strict";
+ load("jstests/libs/override_methods/override_helpers.js");
+
db.getMongo().setCausalConsistency();
db.getMongo().setReadPref("secondary");
- var originalStartParallelShell = startParallelShell;
- startParallelShell = function(jsCode, port, noConnect) {
- var newCode;
- var overridesFile = "jstests/libs/override_methods/enable_causal_consistency.js";
- if (typeof(jsCode) === "function") {
- // Load the override file and immediately invoke the supplied function.
- newCode = `load("${overridesFile}"); (${jsCode})();`;
- } else {
- newCode = `load("${overridesFile}"); ${jsCode};`;
- }
-
- return originalStartParallelShell(newCode, port, noConnect);
- };
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/enable_causal_consistency.js");
})();
diff --git a/jstests/libs/override_methods/enable_sessions.js b/jstests/libs/override_methods/enable_sessions.js
index ebb78f703c3..2d304927c35 100644
--- a/jstests/libs/override_methods/enable_sessions.js
+++ b/jstests/libs/override_methods/enable_sessions.js
@@ -4,6 +4,8 @@
(function() {
"use strict";
+ load("jstests/libs/override_methods/override_helpers.js");
+
var runCommandOriginal = Mongo.prototype.runCommand;
var runCommandWithMetadataOriginal = Mongo.prototype.runCommandWithMetadata;
var getDBOriginal = Mongo.prototype.getDB;
@@ -18,19 +20,8 @@
db = driverSession.getDatabase(db.getName());
sessionMap.set(db.getMongo(), driverSession);
- var originalStartParallelShell = startParallelShell;
- startParallelShell = function(jsCode, port, noConnect) {
- var newCode;
- var overridesFile = "jstests/libs/override_methods/enable_sessions.js";
- if (typeof(jsCode) === "function") {
- // Load the override file and immediately invoke the supplied function.
- newCode = `load("${overridesFile}"); (${jsCode})();`;
- } else {
- newCode = `load("${overridesFile}"); ${jsCode};`;
- }
-
- return originalStartParallelShell(newCode, port, noConnect);
- };
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/enable_sessions.js");
function startSession(conn) {
const driverSession = conn.startSession(sessionOptions);
diff --git a/jstests/libs/override_methods/override_helpers.js b/jstests/libs/override_methods/override_helpers.js
new file mode 100644
index 00000000000..437f1a07669
--- /dev/null
+++ b/jstests/libs/override_methods/override_helpers.js
@@ -0,0 +1,109 @@
+/**
+ * The OverrideHelpers object defines convenience methods for overriding commands and functions in
+ * the mongo shell.
+ */
+var OverrideHelpers = (function() {
+ "use strict";
+
+ function makeIsAggregationWithFirstStage(stageName) {
+ return function(commandName, commandObj) {
+ if (commandName !== "aggregate" || typeof commandObj !== "object" ||
+ commandObj === null) {
+ return false;
+ }
+
+ if (!Array.isArray(commandObj.pipeline) || commandObj.pipeline.length === 0) {
+ return false;
+ }
+
+ const firstStage = commandObj.pipeline[0];
+ if (typeof firstStage !== "object" || firstStage === null) {
+ return false;
+ }
+
+ return Object.keys(firstStage)[0] === stageName;
+ };
+ }
+
+ function isAggregationWithOutStage(commandName, commandObj) {
+ if (commandName !== "aggregate" || typeof commandObj !== "object" || commandObj === null) {
+ return false;
+ }
+
+ if (!Array.isArray(commandObj.pipeline) || commandObj.pipeline.length === 0) {
+ return false;
+ }
+
+ const lastStage = commandObj.pipeline[commandObj.pipeline.length - 1];
+ if (typeof lastStage !== "object" || lastStage === null) {
+ return false;
+ }
+
+ return Object.keys(lastStage)[0] === "$out";
+ }
+
+ function isMapReduceWithInlineOutput(commandName, commandObj) {
+ if ((commandName !== "mapReduce" && commandName !== "mapreduce") ||
+ typeof commandObj !== "object" || commandObj === null) {
+ return false;
+ }
+
+ if (typeof commandObj.out !== "object") {
+ return false;
+ }
+
+ return commandObj.out.hasOwnProperty("inline");
+ }
+
+ function prependOverrideInParallelShell(overrideFile) {
+ const startParallelShellOriginal = startParallelShell;
+
+ startParallelShell = function(jsCode, port, noConnect) {
+ let newCode;
+ if (typeof jsCode === "function") {
+ // Load the override file and immediately invoke the supplied function.
+ newCode = `load("${overrideFile}"); (${jsCode})();`;
+ } else {
+ newCode = `load("${overrideFile}"); ${jsCode};`;
+ }
+
+ return startParallelShellOriginal(newCode, port, noConnect);
+ };
+ }
+
+ function overrideRunCommand(overrideFunc) {
+ const mongoRunCommandOriginal = Mongo.prototype.runCommand;
+ const mongoRunCommandWithMetadataOriginal = Mongo.prototype.runCommandWithMetadata;
+
+ Mongo.prototype.runCommand = function(dbName, commandObj, options) {
+ const commandName = Object.keys(commandObj)[0];
+ return overrideFunc(this,
+ dbName,
+ commandName,
+ commandObj,
+ mongoRunCommandOriginal,
+ (commandObj) => [dbName, commandObj, options]);
+ };
+
+ Mongo.prototype.runCommandWithMetadata = function(dbName, metadata, commandArgs) {
+ const commandName = Object.keys(commandArgs)[0];
+ return overrideFunc(this,
+ dbName,
+ commandName,
+ commandArgs,
+ mongoRunCommandWithMetadataOriginal,
+ (commandArgs) => [dbName, metadata, commandArgs]);
+ };
+ }
+
+ return {
+ isAggregationWithListLocalCursorsStage:
+ makeIsAggregationWithFirstStage("$listLocalCursors"),
+ isAggregationWithListLocalSessionsStage:
+ makeIsAggregationWithFirstStage("$listLocalSessions"),
+ isAggregationWithOutStage: isAggregationWithOutStage,
+ isMapReduceWithInlineOutput: isMapReduceWithInlineOutput,
+ prependOverrideInParallelShell: prependOverrideInParallelShell,
+ overrideRunCommand: overrideRunCommand,
+ };
+})();
diff --git a/jstests/libs/override_methods/retry_writes_at_least_once.js b/jstests/libs/override_methods/retry_writes_at_least_once.js
index f0562c958d1..5e211d88b49 100644
--- a/jstests/libs/override_methods/retry_writes_at_least_once.js
+++ b/jstests/libs/override_methods/retry_writes_at_least_once.js
@@ -6,6 +6,7 @@
(function() {
"use strict";
+ load("jstests/libs/override_methods/override_helpers.js");
load("jstests/libs/retryable_writes_util.js");
Random.setRandomSeed();
@@ -64,18 +65,6 @@
return res;
}
- const startParallelShellOriginal = startParallelShell;
-
- startParallelShell = function(jsCode, port, noConnect) {
- let newCode;
- const overridesFile = "jstests/libs/override_methods/retry_writes_at_least_once.js";
- if (typeof(jsCode) === "function") {
- // Load the override file and immediately invoke the supplied function.
- newCode = `load("${overridesFile}"); (${jsCode})();`;
- } else {
- newCode = `load("${overridesFile}"); ${jsCode};`;
- }
-
- return startParallelShellOriginal(newCode, port, noConnect);
- };
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/retry_writes_at_least_once.js");
})();
diff --git a/jstests/libs/override_methods/set_read_and_write_concerns.js b/jstests/libs/override_methods/set_read_and_write_concerns.js
index a14efa41a7f..00af2133916 100644
--- a/jstests/libs/override_methods/set_read_and_write_concerns.js
+++ b/jstests/libs/override_methods/set_read_and_write_concerns.js
@@ -1,201 +1,221 @@
/**
- * Use prototype overrides to set read concern and write concern while running core tests.
+ * Use prototype overrides to set read concern and write concern while running tests.
*/
(function() {
"use strict";
+ load("jstests/libs/override_methods/override_helpers.js");
+
if (typeof TestData === "undefined" || !TestData.hasOwnProperty("defaultReadConcernLevel")) {
throw new Error(
- "The default read-concern level must be set as the 'defaultReadConcernLevel' " +
- "property on TestData");
- }
- var defaultReadConcern = {level: TestData.defaultReadConcernLevel};
-
- var defaultWriteConcern = {
- w: "majority",
- // Use a "signature" value that won't typically match a value assigned in normal use.
- // This way the wtimeout set by this override is distinguishable in the server logs.
- wtimeout: 5 * 60 * 1000 + 321, // 300321ms
- };
- if (TestData.hasOwnProperty("defaultWriteConcern")) {
- defaultWriteConcern = TestData.defaultWriteConcern;
+ "The readConcern level to use must be set as the 'defaultReadConcernLevel'" +
+ " property on the global TestData object");
}
- var originalDBQuery = DBQuery;
+ const kDefaultReadConcern = {level: TestData.defaultReadConcernLevel};
+ const kDefaultWriteConcern =
+ (TestData.hasOwnProperty("defaultWriteConcern")) ? TestData.defaultWriteConcern : {
+ w: "majority",
+ // Use a "signature" value that won't typically match a value assigned in normal use.
+ // This way the wtimeout set by this override is distinguishable in the server logs.
+ wtimeout: 5 * 60 * 1000 + 321, // 300321ms
+ };
+
+ const kCommandsSupportingReadConcern = new Set([
+ "aggregate",
+ "count",
+ "distinct",
+ "find",
+ "geoNear",
+ "geoSearch",
+ "group",
+ "parallelCollectionScan",
+ ]);
+
+ const kCommandsSupportingWriteConcern = new Set([
+ "_configsvrAddShard",
+ "_configsvrAddShardToZone",
+ "_configsvrCommitChunkMerge",
+ "_configsvrCommitChunkMigration",
+ "_configsvrCommitChunkSplit",
+ "_configsvrCreateDatabase",
+ "_configsvrEnableSharding",
+ "_configsvrMoveChunk",
+ "_configsvrMovePrimary",
+ "_configsvrRemoveShard",
+ "_configsvrRemoveShardFromZone",
+ "_configsvrShardCollection",
+ "_configsvrUpdateZoneKeyRange",
+ "_mergeAuthzCollections",
+ "_recvChunkStart",
+ "appendOplogNote",
+ "applyOps",
+ "aggregate",
+ "captrunc",
+ "cleanupOrphaned",
+ "clone",
+ "cloneCollection",
+ "cloneCollectionAsCapped",
+ "collMod",
+ "convertToCapped",
+ "copydb",
+ "create",
+ "createIndexes",
+ "createRole",
+ "createUser",
+ "delete",
+ "doTxn",
+ "drop",
+ "dropAllRolesFromDatabase",
+ "dropAllUsersFromDatabase",
+ "dropDatabase",
+ "dropIndexes",
+ "dropRole",
+ "dropUser",
+ "emptycapped",
+ "findAndModify",
+ "findandmodify",
+ "godinsert",
+ "grantPrivilegesToRole",
+ "grantRolesToRole",
+ "grantRolesToUser",
+ "insert",
+ "mapReduce",
+ "mapreduce",
+ "mapreduce.shardedfinish",
+ "moveChunk",
+ "renameCollection",
+ "revokePrivilegesFromRole",
+ "revokeRolesFromRole",
+ "revokeRolesFromUser",
+ "setFeatureCompatibilityVersion",
+ "update",
+ "updateRole",
+ "updateUser",
+ ]);
+
+ function runCommandWithReadAndWriteConcerns(
+ conn, dbName, commandName, commandObj, func, makeFuncArgs) {
+ if (typeof commandObj !== "object" || commandObj === null) {
+ return func.apply(conn, makeFuncArgs(commandObj));
+ }
- DBQuery = function(mongo, db, collection, ns, query, fields, limit, skip, batchSize, options) {
- if (ns.endsWith("$cmd")) {
- if (query.hasOwnProperty("writeConcern") &&
- bsonWoCompare(query.writeConcern, defaultWriteConcern) !== 0) {
- jsTestLog("Warning: DBQuery overriding existing writeConcern of: " +
- tojson(query.writeConcern));
- query.writeConcern = defaultWriteConcern;
- }
+ // If the command is in a wrapped form, then we look for the actual command object inside
+ // the query/$query object.
+ let commandObjUnwrapped = commandObj;
+ if (commandName === "query" || commandName === "$query") {
+ commandObjUnwrapped = commandObj[commandName];
+ commandName = Object.keys(commandObjUnwrapped)[0];
}
- return originalDBQuery.apply(this, arguments);
- };
+ if (commandName === "eval" || commandName === "$eval") {
+ throw new Error("Cowardly refusing to run test with overridden write concern when it" +
+ " uses a command that can only perform w=1 writes: " +
+ tojson(commandObj));
+ }
- DBQuery.Option = originalDBQuery.Option;
+ let shouldForceReadConcern = kCommandsSupportingReadConcern.has(commandName);
+ let shouldForceWriteConcern = kCommandsSupportingWriteConcern.has(commandName);
- var originalStartParallelShell = startParallelShell;
- startParallelShell = function(jsCode, port, noConnect) {
- var newCode;
- var overridesFile = "jstests/libs/override_methods/set_read_and_write_concerns.js";
- if (typeof(jsCode) === "function") {
- // Load the override file and immediately invoke the supplied function.
- newCode = `load("${overridesFile}"); (${jsCode})();`;
- } else {
- newCode = `load("${overridesFile}"); ${jsCode};`;
- }
+ if (commandName === "aggregate") {
+ if (OverrideHelpers.isAggregationWithListLocalCursorsStage(commandName,
+ commandObjUnwrapped)) {
+ // The $listLocalCursors stage can only be used with readConcern={level: "local"}.
+ shouldForceReadConcern = false;
+ }
- return originalStartParallelShell(newCode, port, noConnect);
- };
+ if (OverrideHelpers.isAggregationWithListLocalSessionsStage(commandName,
+ commandObjUnwrapped)) {
+ // The $listLocalSessions stage can only be used with readConcern={level: "local"}.
+ shouldForceReadConcern = false;
+ }
- const originalRunCommand = DB.prototype._runCommandImpl;
- DB.prototype._runCommandImpl = function(dbName, obj, options) {
- var cmdName = "";
- for (var fieldName in obj) {
- cmdName = fieldName;
- break;
- }
+ if (OverrideHelpers.isAggregationWithOutStage(commandName, commandObjUnwrapped)) {
+ // The $out stage can only be used with readConcern={level: "local"}.
+ shouldForceReadConcern = false;
+ } else {
+ // A writeConcern can only be used with a $out stage.
+ shouldForceWriteConcern = false;
+ }
- // These commands directly support a writeConcern argument.
- var commandsToForceWriteConcern = [
- "_configsvrAddShard",
- "_configsvrAddShardToZone",
- "_configsvrCommitChunkMerge",
- "_configsvrCommitChunkMigration",
- "_configsvrCommitChunkSplit",
- "_configsvrCreateDatabase",
- "_configsvrEnableSharding",
- "_configsvrMoveChunk",
- "_configsvrMovePrimary",
- "_configsvrRemoveShard",
- "_configsvrRemoveShardFromZone",
- "_configsvrShardCollection",
- "_configsvrUpdateZoneKeyRange",
- "_mergeAuthzCollections",
- "_recvChunkStart",
- "appendOplogNote",
- "applyOps",
- "captrunc",
- "cleanupOrphaned",
- "clone",
- "cloneCollection",
- "cloneCollectionAsCapped",
- "collMod",
- "convertToCapped",
- "copydb",
- "create",
- "createIndexes",
- "createRole",
- "createUser",
- "delete",
- "doTxn",
- "drop",
- "dropAllRolesFromDatabase",
- "dropAllUsersFromDatabase",
- "dropDatabase",
- "dropIndexes",
- "dropRole",
- "dropUser",
- "emptycapped",
- "findAndModify",
- "findandmodify",
- "godinsert",
- "grantPrivilegesToRole",
- "grantRolesToRole",
- "grantRolesToUser",
- "insert",
- "mapreduce.shardedfinish",
- "moveChunk",
- "renameCollection",
- "revokePrivilegesFromRole",
- "revokeRolesFromRole",
- "revokeRolesFromUser",
- "setFeatureCompatibilityVersion",
- "update",
- "updateRole",
- "updateUser",
- ];
-
- // These are reading commands that support majority readConcern.
- var commandsToForceReadConcern = [
- "count",
- "distinct",
- "find",
- "geoNear",
- "geoSearch",
- "group",
- "parallelCollectionScan",
- ];
-
- var forceWriteConcern = Array.contains(commandsToForceWriteConcern, cmdName);
- var forceReadConcern = Array.contains(commandsToForceReadConcern, cmdName);
-
- if (cmdName === "aggregate") {
- // Aggregate can be either a read or a write depending on whether it has a $out stage.
- // $out is required to be the last stage of the pipeline.
- var stages = obj.pipeline;
- const lastStage = stages && Array.isArray(stages) && (stages.length !== 0)
- ? stages[stages.length - 1]
- : undefined;
- const hasOut =
- lastStage && (typeof lastStage === 'object') && lastStage.hasOwnProperty('$out');
- const hasExplain = obj.hasOwnProperty("explain");
- if (!hasExplain) {
- if (hasOut) {
- forceWriteConcern = true;
- } else {
- forceReadConcern = true;
- }
+ if (commandObjUnwrapped.explain) {
+ // Attempting to specify a readConcern while explaining an aggregation would always
+ // return an error prior to SERVER-30582 and it otherwise only compatible with
+ // readConcern={level: "local"}.
+ shouldForceReadConcern = false;
}
+ } else if (OverrideHelpers.isMapReduceWithInlineOutput(commandName, commandObjUnwrapped)) {
+ // A writeConcern can only be used with non-inline output.
+ shouldForceWriteConcern = false;
}
- else if (cmdName === "mapReduce") {
- var stages = obj.pipeline;
- const lastStage = stages && Array.isArray(stages) && (stages.length !== 0)
- ? stages[stages.length - 1]
- : undefined;
- const hasOut =
- lastStage && (typeof lastStage === 'object') && lastStage.hasOwnProperty('$out');
- if (hasOut) {
- forceWriteConcern = true;
+ const inWrappedForm = commandObj !== commandObjUnwrapped;
+
+ if (shouldForceReadConcern) {
+ // We create a copy of 'commandObj' to avoid mutating the parameter the caller
+ // specified.
+ commandObj = Object.assign({}, commandObj);
+ if (inWrappedForm) {
+ commandObjUnwrapped = Object.assign({}, commandObjUnwrapped);
+ commandObj[Object.keys(commandObj)[0]] = commandObjUnwrapped;
+ } else {
+ commandObjUnwrapped = commandObj;
}
- }
- if (forceWriteConcern) {
- if (obj.hasOwnProperty("writeConcern")) {
- if (bsonWoCompare(obj.writeConcern, defaultWriteConcern) !== 0) {
- jsTestLog("Warning: _runCommandImpl overriding existing writeConcern of: " +
- tojson(obj.writeConcern));
- obj.writeConcern = defaultWriteConcern;
+ if (commandObjUnwrapped.hasOwnProperty("readConcern")) {
+ let readConcern = commandObjUnwrapped.readConcern;
+
+ if (typeof readConcern !== "object" || readConcern === null ||
+ (readConcern.hasOwnProperty("level") &&
+ bsonWoCompare({_: readConcern.level}, {_: kDefaultReadConcern.level}) !== 0)) {
+ throw new Error("Cowardly refusing to override read concern of command: " +
+ tojson(commandObj));
}
+
+ // We create a copy of the readConcern object to avoid mutating the parameter the
+ // caller specified.
+ readConcern = Object.assign({}, readConcern, kDefaultReadConcern);
+ commandObjUnwrapped.readConcern = readConcern;
+ } else {
+ commandObjUnwrapped.readConcern = kDefaultReadConcern;
+ }
+ }
+
+ if (shouldForceWriteConcern) {
+ // We create a copy of 'commandObj' to avoid mutating the parameter the caller
+ // specified.
+ commandObj = Object.assign({}, commandObj);
+ if (inWrappedForm) {
+ commandObjUnwrapped = Object.assign({}, commandObjUnwrapped);
+ commandObj[Object.keys(commandObj)[0]] = commandObjUnwrapped;
} else {
- obj.writeConcern = defaultWriteConcern;
+ commandObjUnwrapped = commandObj;
}
- } else if (forceReadConcern) {
- if (obj.hasOwnProperty("readConcern")) {
- if (bsonWoCompare(obj.readConcern, defaultReadConcern) !== 0) {
- jsTestLog("Warning: _runCommandImpl overriding existing readConcern of: " +
- tojson(obj.readConcern));
- obj.readConcern = defaultReadConcern;
+ if (commandObjUnwrapped.hasOwnProperty("writeConcern")) {
+ let writeConcern = commandObjUnwrapped.writeConcern;
+
+ if (typeof writeConcern !== "object" || writeConcern === null ||
+ (writeConcern.hasOwnProperty("w") &&
+ bsonWoCompare({_: writeConcern.w}, {_: kDefaultWriteConcern.w}) !== 0)) {
+ throw new Error("Cowardly refusing to override write concern of command: " +
+ tojson(commandObj));
}
+
+ // We create a copy of the writeConcern object to avoid mutating the parameter the
+ // caller specified.
+ writeConcern = Object.assign({}, writeConcern, kDefaultWriteConcern);
+ commandObjUnwrapped.writeConcern = writeConcern;
} else {
- obj.readConcern = defaultReadConcern;
+ commandObjUnwrapped.writeConcern = kDefaultWriteConcern;
}
}
- var res = originalRunCommand.call(this, dbName, obj, options);
-
- return res;
- };
+ return func.apply(conn, makeFuncArgs(commandObj));
+ }
- // Use a majority write concern if the operation does not specify one.
- DBCollection.prototype.getWriteConcern = function() {
- return new WriteConcern(defaultWriteConcern);
- };
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/set_read_and_write_concerns.js");
+ OverrideHelpers.overrideRunCommand(runCommandWithReadAndWriteConcerns);
})();
diff --git a/jstests/libs/override_methods/set_read_preference_secondary.js b/jstests/libs/override_methods/set_read_preference_secondary.js
index 9955c7f016d..d81532bf8d5 100644
--- a/jstests/libs/override_methods/set_read_preference_secondary.js
+++ b/jstests/libs/override_methods/set_read_preference_secondary.js
@@ -4,31 +4,16 @@
(function() {
"use strict";
- const readPreferenceSecondary = {mode: "secondary"};
+ load("jstests/libs/override_methods/override_helpers.js");
- db.getMongo().setReadPref("secondary");
-
- const originalStartParallelShell = startParallelShell;
- startParallelShell = function(jsCode, port, noConnect) {
- let newCode;
- const overridesFile = "jstests/libs/override_methods/set_read_preference_secondary.js";
- if (typeof(jsCode) === "function") {
- // Load the override file and immediately invoke the supplied function.
- newCode = `load("${overridesFile}"); (${jsCode})();`;
- } else {
- newCode = `load("${overridesFile}"); ${jsCode};`;
- }
-
- return originalStartParallelShell(newCode, port, noConnect);
- };
-
- // These are reading commands that support a read preference.
- const commandsToForceReadPreference = new Set([
+ const kReadPreferenceSecondary = {mode: "secondary"};
+ const kCommandsSupportingReadPreference = new Set([
"aggregate",
"collStats",
"count",
"dbStats",
"distinct",
+ "find",
"geoNear",
"geoSearch",
"group",
@@ -37,33 +22,55 @@
"parallelCollectionScan",
]);
- const originalRunCommand = DB.prototype._runCommandImpl;
- DB.prototype._runCommandImpl = function(dbName, obj, options) {
- const cmdName = Object.keys(obj)[0];
+ function runCommandWithReadPreferenceSecondary(
+ conn, dbName, commandName, commandObj, func, makeFuncArgs) {
+ if (typeof commandObj !== "object" || commandObj === null) {
+ return func.apply(conn, makeFuncArgs(commandObj));
+ }
- let forceReadPreference = commandsToForceReadPreference.has(cmdName);
- if (cmdName === "aggregate" && obj.pipeline && Array.isArray(obj.pipeline) &&
- obj.pipeline.length > 0 &&
- obj.pipeline[obj.pipeline.length - 1].hasOwnProperty("$out")) {
- forceReadPreference = false;
- } else if ((cmdName === "mapReduce" || cmdName === "mapreduce") &&
- obj.hasOwnProperty("out") && typeof obj.out === "object" && !obj.out.inline) {
- forceReadPreference = false;
+ // If the command is in a wrapped form, then we look for the actual command object inside
+ // the query/$query object.
+ let commandObjUnwrapped = commandObj;
+ if (commandName === "query" || commandName === "$query") {
+ commandObjUnwrapped = commandObj[commandName];
+ commandName = Object.keys(commandObjUnwrapped)[0];
}
- if (forceReadPreference) {
- if (obj.hasOwnProperty("$readPreference")) {
- if (bsonWoCompare(obj.$readPreference, readPreferenceSecondary) !== 0) {
- jsTestLog("Warning: _runCommandImpl overriding existing $readPreference of: " +
- tojson(obj.$readPreference));
- obj.$readPreference = readPreferenceSecondary;
- }
+ let shouldForceReadPreference = kCommandsSupportingReadPreference.has(commandName);
+ if (OverrideHelpers.isAggregationWithOutStage(commandName, commandObjUnwrapped)) {
+ // An aggregation with a $out stage must be sent to the primary.
+ shouldForceReadPreference = false;
+ } else if ((commandName === "mapReduce" || commandName === "mapreduce") &&
+ !OverrideHelpers.isMapReduceWithInlineOutput(commandName, commandObjUnwrapped)) {
+ // A map-reduce operation with non-inline output must be sent to the primary.
+ shouldForceReadPreference = false;
+ }
+
+ if (shouldForceReadPreference) {
+ if (commandObj === commandObjUnwrapped) {
+ // We wrap the command object using a "query" field rather than a "$query" field to
+ // match the implementation of DB.prototype._attachReadPreferenceToCommand().
+ commandObj = {query: commandObj};
} else {
- obj.$readPreference = readPreferenceSecondary;
+ // We create a copy of 'commandObj' to avoid mutating the parameter the caller
+ // specified.
+ commandObj = Object.assign({}, commandObj);
+ }
+
+ if (commandObj.hasOwnProperty("$readPreference") &&
+ !bsonBinaryEqual({_: commandObj.$readPreference}, {_: kReadPreferenceSecondary})) {
+ throw new Error("Cowardly refusing to override read preference of command: " +
+ tojson(commandObj));
}
+
+ commandObj.$readPreference = kReadPreferenceSecondary;
}
- return originalRunCommand.call(this, dbName, obj, options);
- };
+ return func.apply(conn, makeFuncArgs(commandObj));
+ }
+
+ OverrideHelpers.prependOverrideInParallelShell(
+ "jstests/libs/override_methods/set_read_preference_secondary.js");
+ OverrideHelpers.overrideRunCommand(runCommandWithReadPreferenceSecondary);
})();