diff options
author | Max Hirschhorn <max.hirschhorn@mongodb.com> | 2018-02-12 11:47:50 -0500 |
---|---|---|
committer | Max Hirschhorn <max.hirschhorn@mongodb.com> | 2018-02-12 11:47:50 -0500 |
commit | b2399471de27583b684a1b4ad67195cab7062865 (patch) | |
tree | 59f9952a28dbb5a8a73d87a12d52bb242718beb9 | |
parent | 3536849e28ba1d01eda1b280abd4ef62087b7107 (diff) | |
download | mongo-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)
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( |