summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Hirschhorn <max.hirschhorn@mongodb.com>2018-02-12 11:47:50 -0500
committerMax Hirschhorn <max.hirschhorn@mongodb.com>2018-02-12 11:47:50 -0500
commitb2399471de27583b684a1b4ad67195cab7062865 (patch)
tree59f9952a28dbb5a8a73d87a12d52bb242718beb9
parent3536849e28ba1d01eda1b280abd4ef62087b7107 (diff)
downloadmongo-b2399471de27583b684a1b4ad67195cab7062865.tar.gz
SERVER-32691 Add write_concern_majority_passthrough.yml test suite.
Also adds support for using replica set connection strings in resmoke.py without making all nodes electable. (cherry picked from commit 264d971842cffdf8b4f80def1d90241f132345b7)
-rw-r--r--buildscripts/resmokeconfig/suites/change_streams_secondary_reads.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml72
-rw-r--r--buildscripts/resmokelib/testing/fixtures/replicaset.py11
-rw-r--r--jstests/core/capped6.js10
-rw-r--r--jstests/core/collation_plan_cache.js12
-rw-r--r--jstests/core/count10.js10
-rw-r--r--jstests/core/count_plan_summary.js13
-rw-r--r--jstests/core/geo_s2cursorlimitskip.js11
-rw-r--r--jstests/core/getlog2.js10
-rw-r--r--jstests/core/index_filter_commands.js28
-rw-r--r--jstests/core/index_stats.js16
-rw-r--r--jstests/core/list_all_local_sessions.js15
-rw-r--r--jstests/core/list_local_sessions.js15
-rw-r--r--jstests/core/max_time_ms.js10
-rw-r--r--jstests/core/mr_optim.js15
-rw-r--r--jstests/core/notablescan.js10
-rw-r--r--jstests/core/operation_latency_histogram.js6
-rw-r--r--jstests/core/plan_cache_clear.js10
-rw-r--r--jstests/core/plan_cache_list_plans.js10
-rw-r--r--jstests/core/plan_cache_list_shapes.js10
-rw-r--r--jstests/core/plan_cache_shell_helpers.js10
-rw-r--r--jstests/core/startup_log.js7
-rw-r--r--jstests/core/top.js6
-rw-r--r--jstests/core/views/views_all_commands.js9
-rw-r--r--jstests/core/views/views_stats.js6
-rw-r--r--jstests/libs/override_methods/set_read_preference_secondary.js88
26 files changed, 363 insertions, 59 deletions
diff --git a/buildscripts/resmokeconfig/suites/change_streams_secondary_reads.yml b/buildscripts/resmokeconfig/suites/change_streams_secondary_reads.yml
index 7f84c527d78..4157fe562d1 100644
--- a/buildscripts/resmokeconfig/suites/change_streams_secondary_reads.yml
+++ b/buildscripts/resmokeconfig/suites/change_streams_secondary_reads.yml
@@ -29,6 +29,8 @@ selector:
# are the message(s) that cause the tag to be warranted.
##
# "Cowardly refusing to override read preference of command: ..."
+ # "Cowardly refusing to run test with overridden read preference when it reads from a
+ # non-replicated collection: ..."
- assumes_read_preference_unchanged
executor:
diff --git a/buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml b/buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml
new file mode 100644
index 00000000000..1e90e127a21
--- /dev/null
+++ b/buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml
@@ -0,0 +1,72 @@
+test_kind: js_test
+
+selector:
+ roots:
+ - jstests/core/**/*.js
+ exclude_files:
+ # These tests are not expected to pass with replica-sets:
+ - jstests/core/dbadmin.js
+ - jstests/core/opcounters_write_cmd.js
+ - jstests/core/read_after_optime.js
+ - jstests/core/capped_update.js
+ # These tests attempt to read from the "system.profile" collection, which may be missing entries
+ # if a write was performed on the primary of the replica set instead.
+ - jstests/core/*profile*.js
+ # The shellkillop.js test spawns a parallel shell without using startParallelShell() and therefore
+ # doesn't inherit the w="majority" write concern when performing its writes.
+ - jstests/core/shellkillop.js
+ exclude_with_any_tags:
+ ##
+ # The next three tags correspond to the special errors thrown by the
+ # set_read_and_write_concerns.js override when it refuses to replace the readConcern or
+ # writeConcern of a particular command. Above each tag are the message(s) that cause the tag to be
+ # warranted.
+ ##
+ # "Cowardly refusing to override read concern of command: ..."
+ - assumes_read_concern_unchanged
+ # "Cowardly refusing to override write concern of command: ..."
+ - assumes_write_concern_unchanged
+ # "Cowardly refusing to run test with overridden write concern when it uses a command that can
+ # only perform w=1 writes: ..."
+ - requires_eval_command
+ ##
+ # The next tag corresponds to the special error thrown by the set_read_preference_secondary.js
+ # override when it refuses to replace the readPreference of a particular command. Above each tag
+ # are the message(s) that cause the tag to be warranted.
+ ##
+ # "Cowardly refusing to override read preference of command: ..."
+ # "Cowardly refusing to run test with overridden read preference when it reads from a
+ # non-replicated collection: ..."
+ - assumes_read_preference_unchanged
+
+executor:
+ config:
+ shell_options:
+ global_vars:
+ TestData:
+ defaultReadConcernLevel: local
+ eval: >-
+ testingReplication = true;
+ load('jstests/libs/override_methods/set_read_and_write_concerns.js');
+ load('jstests/libs/override_methods/set_read_preference_secondary.js');
+ readMode: commands
+ hooks:
+ # The CheckReplDBHash hook waits until all operations have replicated to and have been applied
+ # on the secondaries, so we run the ValidateCollections hook after it to ensure we're
+ # validating the entire contents of the collection.
+ - class: CheckReplOplogs
+ - class: CheckReplDBHash
+ - class: ValidateCollections
+ - class: CleanEveryN
+ n: 20
+ fixture:
+ class: ReplicaSetFixture
+ mongod_options:
+ set_parameters:
+ enableTestCommands: 1
+ numInitialSyncAttempts: 1
+ # This suite requires w="majority" writes to be applied on all secondaries. By using a 2-node
+ # replica set and having secondaries vote, the majority of the replica set is all nodes.
+ num_nodes: 2
+ voting_secondaries: true
+ use_replica_set_connection_string: true
diff --git a/buildscripts/resmokelib/testing/fixtures/replicaset.py b/buildscripts/resmokelib/testing/fixtures/replicaset.py
index fd4ff0bfdb5..0a531220311 100644
--- a/buildscripts/resmokelib/testing/fixtures/replicaset.py
+++ b/buildscripts/resmokelib/testing/fixtures/replicaset.py
@@ -38,7 +38,8 @@ class ReplicaSetFixture(interface.ReplFixture):
auth_options=None,
replset_config_options=None,
voting_secondaries=None,
- all_nodes_electable=False):
+ all_nodes_electable=False,
+ use_replica_set_connection_string=None):
interface.ReplFixture.__init__(self, logger, job_num)
@@ -52,12 +53,18 @@ class ReplicaSetFixture(interface.ReplFixture):
self.replset_config_options = utils.default_if_none(replset_config_options, {})
self.voting_secondaries = voting_secondaries
self.all_nodes_electable = all_nodes_electable
+ self.use_replica_set_connection_string = use_replica_set_connection_string
# If voting_secondaries has not been set, set a default. By default, secondaries have zero
# votes unless they are also nodes capable of being elected primary.
if self.voting_secondaries is None:
self.voting_secondaries = self.all_nodes_electable
+ # By default, we only use a replica set connection string if all nodes are capable of being
+ # elected primary.
+ if self.use_replica_set_connection_string is None:
+ self.use_replica_set_connection_string = self.all_nodes_electable
+
# The dbpath in mongod_options is used as the dbpath prefix for replica set members and
# takes precedence over other settings. The ShardedClusterFixture uses this parameter to
# create replica sets and assign their dbpath structure explicitly.
@@ -336,7 +343,7 @@ class ReplicaSetFixture(interface.ReplFixture):
if self.replset_name is None:
raise ValueError("Must call setup() before calling get_driver_connection_url()")
- if self.all_nodes_electable:
+ if self.use_replica_set_connection_string:
# We use a replica set connection string when all nodes are electable because we
# anticipate the client will want to gracefully handle any failovers.
conn_strs = [node.get_internal_connection_string() for node in self.nodes]
diff --git a/jstests/core/capped6.js b/jstests/core/capped6.js
index 945567ee976..c92006f0d01 100644
--- a/jstests/core/capped6.js
+++ b/jstests/core/capped6.js
@@ -1,6 +1,12 @@
-// @tags: [requires_non_retryable_commands]
-
// Test NamespaceDetails::cappedTruncateAfter via "captrunc" command
+//
+// @tags: [
+// # This test attempts to perform read operations on a capped collection after truncating
+// # documents using the captrunc command. The former operations may be routed to a secondary in
+// # the replica set, whereas the latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// requires_non_retryable_commands,
+// ]
(function() {
var coll = db.capped6;
diff --git a/jstests/core/collation_plan_cache.js b/jstests/core/collation_plan_cache.js
index b28747a88cf..d75c6f215b3 100644
--- a/jstests/core/collation_plan_cache.js
+++ b/jstests/core/collation_plan_cache.js
@@ -1,6 +1,12 @@
-// @tags: [does_not_support_stepdowns]
-
// Integration testing for the plan cache and index filter commands with collation.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect the server's plan cache entries. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
(function() {
'use strict';
@@ -240,4 +246,4 @@
assert.eq(0,
coll.runCommand('planCacheListFilters').filters.length,
'unexpected number of plan cache filters');
-})(); \ No newline at end of file
+})();
diff --git a/jstests/core/count10.js b/jstests/core/count10.js
index 5e3bf10a8af..75838521527 100644
--- a/jstests/core/count10.js
+++ b/jstests/core/count10.js
@@ -1,6 +1,12 @@
-// @tags: [does_not_support_stepdowns]
-
// Test that interrupting a count returns an error code.
+//
+// @tags: [
+// # This test attempts to perform a count command and find it using the currentOp command. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
t = db.count10;
t.drop();
diff --git a/jstests/core/count_plan_summary.js b/jstests/core/count_plan_summary.js
index d252c00919a..b928eb287fc 100644
--- a/jstests/core/count_plan_summary.js
+++ b/jstests/core/count_plan_summary.js
@@ -1,7 +1,12 @@
-// @tags: [does_not_support_stepdowns]
-
-// Test that the plan summary string appears in db.currentOp() for
-// count operations. SERVER-14064.
+// Test that the plan summary string appears in db.currentOp() for count operations. SERVER-14064.
+//
+// @tags: [
+// # This test attempts to perform a find command and find it using the currentOp command. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
var t = db.jstests_count_plan_summary;
t.drop();
diff --git a/jstests/core/geo_s2cursorlimitskip.js b/jstests/core/geo_s2cursorlimitskip.js
index dab23124e86..ab907919108 100644
--- a/jstests/core/geo_s2cursorlimitskip.js
+++ b/jstests/core/geo_s2cursorlimitskip.js
@@ -1,6 +1,13 @@
-// @tags: [does_not_support_stepdowns, requires_getmore]
-
// Test various cursor behaviors
+//
+// @tags: [
+// # This test attempts to enable profiling on a server and then get profiling data by reading
+// # from the "system.profile" collection. The former operation must be routed to the primary in
+// # a replica set, whereas the latter may be routed to a secondary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// requires_getmore,
+// ]
var testDB = db.getSiblingDB("geo_s2cursorlimitskip");
var t = testDB.geo_s2getmmm;
diff --git a/jstests/core/getlog2.js b/jstests/core/getlog2.js
index 987992dbd67..46514813732 100644
--- a/jstests/core/getlog2.js
+++ b/jstests/core/getlog2.js
@@ -1,6 +1,12 @@
-// @tags: [does_not_support_stepdowns]
-
// tests getlog as well as slow querying logging
+//
+// @tags: [
+// # This test attempts to perform a find command and see that it ran using the getLog command.
+// # The former operation may be routed to a secondary in the replica set, whereas the latter must
+// # be routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
// We turn off gossiping the mongo shell's clusterTime because it causes the slow command log
// messages to get truncated since they'll exceed 512 characters. The truncated log messages will
diff --git a/jstests/core/index_filter_commands.js b/jstests/core/index_filter_commands.js
index cc50f65ca39..7a9680a6361 100644
--- a/jstests/core/index_filter_commands.js
+++ b/jstests/core/index_filter_commands.js
@@ -1,7 +1,3 @@
-// Cannot implicitly shard accessed collections because of collection existing when none
-// expected.
-// @tags: [assumes_no_implicit_collection_creation_after_drop, does_not_support_stepdowns]
-
/**
* Index Filter commands
*
@@ -10,20 +6,28 @@
* Displays index filters for all query shapes in a collection.
*
* - planCacheClearFilters
- * Clears index filter for a single query shape or,
- * if the query shape is omitted, all filters for the collection.
+ * Clears index filter for a single query shape or, if the query shape is omitted, all filters for
+ * the collection.
*
* - planCacheSetFilter
* Sets index filter for a query shape. Overrides existing filter.
*
- * Not a lot of data access in this test suite. Hint commands
- * manage a non-persistent mapping in the server of
- * query shape to list of index specs.
+ * Not a lot of data access in this test suite. Hint commands manage a non-persistent mapping in the
+ * server of query shape to list of index specs.
*
- * Only time we might need to execute a query is to check the plan
- * cache state. We would do this with the planCacheListPlans command
- * on the same query shape with the index filters.
+ * Only time we might need to execute a query is to check the plan cache state. We would do this
+ * with the planCacheListPlans command on the same query shape with the index filters.
*
+ * @tags: [
+ * # Cannot implicitly shard accessed collections because of collection existing when none
+ * # expected.
+ * assumes_no_implicit_collection_creation_after_drop,
+ * # This test attempts to perform queries with plan cache filters set up. The former operation
+ * # may be routed to a secondary in the replica set, whereas the latter must be routed to the
+ * # primary.
+ * assumes_read_preference_unchanged,
+ * does_not_support_stepdowns,
+ * ]
*/
load("jstests/libs/analyze_plan.js");
diff --git a/jstests/core/index_stats.js b/jstests/core/index_stats.js
index 4781484132f..69517475191 100644
--- a/jstests/core/index_stats.js
+++ b/jstests/core/index_stats.js
@@ -1,7 +1,15 @@
-// Cannot implicitly shard accessed collections because of following errmsg: A single
-// update/delete on a sharded collection must contain an exact match on _id or contain the shard
-// key.
-// @tags: [assumes_unsharded_collection, does_not_support_stepdowns, requires_non_retryable_writes]
+// @tags: [
+// # Cannot implicitly shard accessed collections because of following errmsg: A single
+// # update/delete on a sharded collection must contain an exact match on _id or contain the shard
+// # key.
+// assumes_unsharded_collection,
+// # This test attempts to perform write operations and get index usage statistics using the
+// # $indexStats stage. The former operation must be routed to the primary in a replica set,
+// # whereas the latter may be routed to a secondary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// requires_non_retryable_writes,
+// ]
(function() {
"use strict";
diff --git a/jstests/core/list_all_local_sessions.js b/jstests/core/list_all_local_sessions.js
index a23df65f3dd..cf9b25ad4e9 100644
--- a/jstests/core/list_all_local_sessions.js
+++ b/jstests/core/list_all_local_sessions.js
@@ -1,9 +1,16 @@
// Basic tests for the $listLocalSessions {allUsers: true} aggregation stage.
//
-// Sessions are asynchronously flushed to disk, so a stepdown immediately after calling
-// startSession may cause this test to fail to find the returned sessionId.
-// Uses features that require featureCompatibilityVersion 3.6.
-// @tags: [does_not_support_stepdowns, requires_fcv36]
+// @tags: [
+// # This test attempts to start a session and find it using the $listLocalSessions stage. The
+// # former operation must be routed to the primary in a replica set, whereas the latter may be
+// # routed to a secondary.
+// assumes_read_preference_unchanged,
+// # Sessions are asynchronously flushed to disk, so a stepdown immediately after calling
+// # startSession may cause this test to fail to find the returned sessionId.
+// does_not_support_stepdowns,
+// # Usage of sessions requires featureCompatibilityVersion=3.6.
+// requires_fcv36,
+// ]
(function() {
'use strict';
diff --git a/jstests/core/list_local_sessions.js b/jstests/core/list_local_sessions.js
index c1bdb6724c4..b2c4af5ce2c 100644
--- a/jstests/core/list_local_sessions.js
+++ b/jstests/core/list_local_sessions.js
@@ -1,9 +1,16 @@
// Basic tests for the $listLocalSessions aggregation stage.
//
-// Sessions are asynchronously flushed to disk, so a stepdown immediately after calling
-// startSession may cause this test to fail to find the returned sessionId.
-// Uses features that require featureCompatibilityVersion 3.6.
-// @tags: [does_not_support_stepdowns, requires_fcv36]
+// @tags: [
+// # This test attempts to start a session and find it using the $listLocalSessions stage. The
+// # former operation must be routed to the primary in a replica set, whereas the latter may be
+// # routed to a secondary.
+// assumes_read_preference_unchanged,
+// # Sessions are asynchronously flushed to disk, so a stepdown immediately after calling
+// # startSession may cause this test to fail to find the returned sessionId.
+// does_not_support_stepdowns,
+// # Usage of sessions requires featureCompatibilityVersion=3.6.
+// requires_fcv36,
+// ]
(function() {
'use strict';
diff --git a/jstests/core/max_time_ms.js b/jstests/core/max_time_ms.js
index 468a474aef3..f10ffb0bce1 100644
--- a/jstests/core/max_time_ms.js
+++ b/jstests/core/max_time_ms.js
@@ -1,6 +1,12 @@
-// @tags: [requires_getmore]
-
// Tests query/command option $maxTimeMS.
+//
+// @tags: [
+// # This test attempts to perform read operations after having enabled the maxTimeAlwaysTimeOut
+// # failpoint. The former operations may be routed to a secondary in the replica set, whereas the
+// # latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// requires_getmore,
+// ]
var t = db.max_time_ms;
var exceededTimeLimit = 50; // ErrorCodes::ExceededTimeLimit
diff --git a/jstests/core/mr_optim.js b/jstests/core/mr_optim.js
index 19ec4e55b6b..d0fc6c8d371 100644
--- a/jstests/core/mr_optim.js
+++ b/jstests/core/mr_optim.js
@@ -3,8 +3,17 @@
t = db.mr_optim;
t.drop();
+// We drop the output collection to ensure the test can be run multiple times successfully. We
+// explicitly avoid using the DBCollection#drop() shell helper to avoid implicitly sharding the
+// collection during the sharded_collections_jscore_passthrough.yml test suite when reading the
+// results from the output collection in the reformat() function.
+var res = db.runCommand({drop: "mr_optim_out"});
+if (res.ok !== 1) {
+ assert.commandFailedWithCode(res, ErrorCodes.NamespaceNotFound);
+}
+
for (var i = 0; i < 1000; ++i) {
- t.save({a: Math.random(1000), b: Math.random(10000)});
+ assert.writeOK(t.save({a: Math.random(1000), b: Math.random(10000)}));
}
function m() {
@@ -21,7 +30,7 @@ function reformat(r) {
if (r.results)
cursor = r.results;
else
- cursor = r.find();
+ cursor = r.find().sort({_id: 1});
cursor.forEach(function(z) {
x[z._id] = z.value;
});
@@ -43,4 +52,4 @@ res.drop();
assert.eq(x, x2, "object from inline and collection are not equal");
-t.drop(); \ No newline at end of file
+t.drop();
diff --git a/jstests/core/notablescan.js b/jstests/core/notablescan.js
index d6ff16fc1f5..69da1e9c547 100644
--- a/jstests/core/notablescan.js
+++ b/jstests/core/notablescan.js
@@ -1,6 +1,12 @@
-// @tags: [does_not_support_stepdowns]
-
// check notablescan mode
+//
+// @tags: [
+// # This test attempts to perform read operations after having enabled the notablescan server
+// # parameter. The former operations may be routed to a secondary in the replica set, whereas the
+// # latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
t = db.test_notablescan;
t.drop();
diff --git a/jstests/core/operation_latency_histogram.js b/jstests/core/operation_latency_histogram.js
index 64d6bd6a33a..e0fc3dfaf6c 100644
--- a/jstests/core/operation_latency_histogram.js
+++ b/jstests/core/operation_latency_histogram.js
@@ -1,4 +1,10 @@
// Checks that histogram counters for collections are updated as we expect.
+//
+// This test attempts to perform write operations and get latency statistics using the $collStats
+// stage. The former operation must be routed to the primary in a replica set, whereas the latter
+// may be routed to a secondary.
+//
+// @tags: [assumes_read_preference_unchanged]
(function() {
"use strict";
diff --git a/jstests/core/plan_cache_clear.js b/jstests/core/plan_cache_clear.js
index a03ec7fb08c..77a9e407f50 100644
--- a/jstests/core/plan_cache_clear.js
+++ b/jstests/core/plan_cache_clear.js
@@ -1,7 +1,13 @@
-// @tags: [does_not_support_stepdowns]
-
// Test clearing of the plan cache, either manually through the planCacheClear command,
// or due to system events such as an index build.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect/manipulate the server's plan cache
+// # entries. The former operation may be routed to a secondary in the replica set, whereas the
+// # latter must be routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
var t = db.jstests_plan_cache_clear;
t.drop();
diff --git a/jstests/core/plan_cache_list_plans.js b/jstests/core/plan_cache_list_plans.js
index e82f8f1cdd4..caa1cc9cd55 100644
--- a/jstests/core/plan_cache_list_plans.js
+++ b/jstests/core/plan_cache_list_plans.js
@@ -1,6 +1,12 @@
-// @tags: [does_not_support_stepdowns]
-
// Test the planCacheListPlans command.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect the server's plan cache entries. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
(function() {
"use strict";
diff --git a/jstests/core/plan_cache_list_shapes.js b/jstests/core/plan_cache_list_shapes.js
index f78021c54ea..8e873f4894c 100644
--- a/jstests/core/plan_cache_list_shapes.js
+++ b/jstests/core/plan_cache_list_shapes.js
@@ -1,7 +1,13 @@
-// @tags: [does_not_support_stepdowns]
-
// Test the planCacheListQueryShapes command, which returns a list of query shapes
// for the queries currently cached in the collection.
+//
+// @tags: [
+// # This test attempts to perform queries with plan cache filters set up. The former operation
+// # may be routed to a secondary in the replica set, whereas the latter must be routed to the
+// # primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
var t = db.jstests_plan_cache_list_shapes;
t.drop();
diff --git a/jstests/core/plan_cache_shell_helpers.js b/jstests/core/plan_cache_shell_helpers.js
index 6ffbae8bed5..8f7399fea54 100644
--- a/jstests/core/plan_cache_shell_helpers.js
+++ b/jstests/core/plan_cache_shell_helpers.js
@@ -1,6 +1,12 @@
-// @tags: [does_not_support_stepdowns]
-
// Test the shell helpers which wrap the plan cache commands.
+//
+// @tags: [
+// # This test attempts to perform queries and introspect the server's plan cache entries. The
+// # former operation may be routed to a secondary in the replica set, whereas the latter must be
+// # routed to the primary.
+// assumes_read_preference_unchanged,
+// does_not_support_stepdowns,
+// ]
var t = db.jstests_plan_cache_shell_helpers;
t.drop();
diff --git a/jstests/core/startup_log.js b/jstests/core/startup_log.js
index aafae9bd8e4..b869221e7dd 100644
--- a/jstests/core/startup_log.js
+++ b/jstests/core/startup_log.js
@@ -1,3 +1,10 @@
+/**
+ * This test attempts to read from the "local.startup_log" collection and assert that it has an
+ * entry matching the server's response from the "getCmdLineOpts" command. The former operation may
+ * be routed to a secondary in the replica set, whereas the latter must be routed to the primary.
+ *
+ * @tags: [assumes_read_preference_unchanged]
+ */
load('jstests/aggregation/extras/utils.js');
(function() {
diff --git a/jstests/core/top.js b/jstests/core/top.js
index 819b41b0981..3d98f5a7b2d 100644
--- a/jstests/core/top.js
+++ b/jstests/core/top.js
@@ -1,5 +1,11 @@
/**
* 1. check top numbers are correct
+ *
+ * This test attempts to perform read operations and get statistics using the top command. The
+ * former operation may be routed to a secondary in the replica set, whereas the latter must be
+ * routed to the primary.
+ *
+ * @tags: [assumes_read_preference_unchanged]
*/
(function() {
load("jstests/libs/stats.js");
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js
index bff86c4aca2..d2a7aa30d53 100644
--- a/jstests/core/views/views_all_commands.js
+++ b/jstests/core/views/views_all_commands.js
@@ -306,14 +306,19 @@
},
command: function(conn) {
// First get and check a partial result for an aggregate command.
- let aggCmd = {aggregate: "view", pipeline: [], cursor: {batchSize: 2}};
+ let aggCmd = {
+ aggregate: "view",
+ pipeline: [{$sort: {_id: 1}}],
+ cursor: {batchSize: 2}
+ };
let res = conn.runCommand(aggCmd);
assert.commandWorked(res, aggCmd);
let cursor = res.cursor;
assert.eq(
cursor.ns, "test.view", "expected view namespace in cursor: " + tojson(cursor));
let expectedFirstBatch = [{_id: 1}, {_id: 2}];
- assert.eq(cursor.firstBatch, expectedFirstBatch, "find returned wrong firstBatch");
+ assert.eq(
+ cursor.firstBatch, expectedFirstBatch, "aggregate returned wrong firstBatch");
// Then check correct execution of the killCursors command.
let killCursorsCmd = {killCursors: "view", cursors: [cursor.id]};
diff --git a/jstests/core/views/views_stats.js b/jstests/core/views/views_stats.js
index 75feb857c9a..22261e9fa81 100644
--- a/jstests/core/views/views_stats.js
+++ b/jstests/core/views/views_stats.js
@@ -1,4 +1,10 @@
// Test that top and latency histogram statistics are recorded for views.
+//
+// This test attempts to perform write operations and get latency statistics using the $collStats
+// stage. The former operation must be routed to the primary in a replica set, whereas the latter
+// may be routed to a secondary.
+//
+// @tags: [assumes_read_preference_unchanged]
(function() {
"use strict";
diff --git a/jstests/libs/override_methods/set_read_preference_secondary.js b/jstests/libs/override_methods/set_read_preference_secondary.js
index d81532bf8d5..d1d26433c5c 100644
--- a/jstests/libs/override_methods/set_read_preference_secondary.js
+++ b/jstests/libs/override_methods/set_read_preference_secondary.js
@@ -22,6 +22,33 @@
"parallelCollectionScan",
]);
+ // This list of cursor-generating commands is incomplete. For example, "listCollections",
+ // "listIndexes", "parallelCollectionScan", and "repairCursor" are all missing from this list.
+ // If we ever add tests that attempt to run getMore or killCursors on cursors generated from
+ // those commands, then we should update the contents of this list and also handle any
+ // differences in the server's response format.
+ const kCursorGeneratingCommands = new Set(["aggregate", "find"]);
+
+ const CursorTracker = (function() {
+ const kNoCursor = new NumberLong(0);
+
+ const connectionsByCursorId = {};
+
+ return {
+ getConnectionUsedForCursor: function getConnectionUsedForCursor(cursorId) {
+ return (cursorId instanceof NumberLong) ? connectionsByCursorId[cursorId]
+ : undefined;
+ },
+
+ setConnectionUsedForCursor: function setConnectionUsedForCursor(cursorId, cursorConn) {
+ if (cursorId instanceof NumberLong &&
+ !bsonBinaryEqual({_: cursorId}, {_: kNoCursor})) {
+ connectionsByCursorId[cursorId] = cursorConn;
+ }
+ },
+ };
+ })();
+
function runCommandWithReadPreferenceSecondary(
conn, dbName, commandName, commandObj, func, makeFuncArgs) {
if (typeof commandObj !== "object" || commandObj === null) {
@@ -36,6 +63,54 @@
commandName = Object.keys(commandObjUnwrapped)[0];
}
+ if (commandObj[commandName] === "system.profile") {
+ throw new Error("Cowardly refusing to run test with overridden read preference" +
+ " when it reads from a non-replicated collection: " +
+ tojson(commandObj));
+ }
+
+ if (conn.isReplicaSetConnection()) {
+ // When a "getMore" or "killCursors" command is issued on a replica set connection, we
+ // attempt to automatically route the command to the server the cursor(s) were
+ // originally established on. This makes it possible to use the
+ // set_read_preference_secondary.js override without needing to update calls of
+ // DB#runCommand() to explicitly track the connection that was used. If the connection
+ // is actually a direct connection to a mongod or mongos process, or if the cursor id
+ // cannot be found in the CursorTracker, then we'll fall back to using DBClientRS's
+ // server selection and send the operation to the current primary. It is possible that
+ // the test is trying to exercise the behavior around when an unknown cursor id is sent
+ // to the server.
+ if (commandName === "getMore") {
+ const cursorId = commandObjUnwrapped[commandName];
+ const cursorConn = CursorTracker.getConnectionUsedForCursor(cursorId);
+ if (cursorConn !== undefined) {
+ return func.apply(cursorConn, makeFuncArgs(commandObj));
+ }
+ } else if (commandName === "killCursors") {
+ const cursorIds = commandObjUnwrapped.cursors;
+ if (Array.isArray(cursorIds)) {
+ let cursorConn;
+
+ for (let cursorId of cursorIds) {
+ const otherCursorConn = CursorTracker.getConnectionUsedForCursor(cursorId);
+ if (cursorConn === undefined) {
+ cursorConn = otherCursorConn;
+ } else if (otherCursorConn !== undefined) {
+ // We set 'cursorConn' back to undefined and break out of the loop so
+ // that we don't attempt to automatically route the "killCursors"
+ // command when there are cursors from different servers.
+ cursorConn = undefined;
+ break;
+ }
+ }
+
+ if (cursorConn !== undefined) {
+ return func.apply(cursorConn, makeFuncArgs(commandObj));
+ }
+ }
+ }
+ }
+
let shouldForceReadPreference = kCommandsSupportingReadPreference.has(commandName);
if (OverrideHelpers.isAggregationWithOutStage(commandName, commandObjUnwrapped)) {
// An aggregation with a $out stage must be sent to the primary.
@@ -66,7 +141,18 @@
commandObj.$readPreference = kReadPreferenceSecondary;
}
- return func.apply(conn, makeFuncArgs(commandObj));
+ const serverResponse = func.apply(conn, makeFuncArgs(commandObj));
+
+ if (conn.isReplicaSetConnection() && kCursorGeneratingCommands.has(commandName) &&
+ serverResponse.ok === 1 && serverResponse.hasOwnProperty("cursor")) {
+ // We associate the cursor id returned by the server with the connection that was used
+ // to establish it so that we can attempt to automatically route subsequent "getMore"
+ // and "killCursors" commands.
+ CursorTracker.setConnectionUsedForCursor(serverResponse.cursor.id,
+ serverResponse._mongo);
+ }
+
+ return serverResponse;
}
OverrideHelpers.prependOverrideInParallelShell(