From b2399471de27583b684a1b4ad67195cab7062865 Mon Sep 17 00:00:00 2001 From: Max Hirschhorn Date: Mon, 12 Feb 2018 11:47:50 -0500 Subject: 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) --- .../suites/change_streams_secondary_reads.yml | 2 + .../suites/write_concern_majority_passthrough.yml | 72 ++++++++++++++++++ .../resmokelib/testing/fixtures/replicaset.py | 11 ++- jstests/core/capped6.js | 10 ++- jstests/core/collation_plan_cache.js | 12 ++- jstests/core/count10.js | 10 ++- jstests/core/count_plan_summary.js | 13 +++- jstests/core/geo_s2cursorlimitskip.js | 11 ++- jstests/core/getlog2.js | 10 ++- jstests/core/index_filter_commands.js | 28 ++++--- jstests/core/index_stats.js | 16 +++- jstests/core/list_all_local_sessions.js | 15 +++- jstests/core/list_local_sessions.js | 15 +++- jstests/core/max_time_ms.js | 10 ++- jstests/core/mr_optim.js | 15 +++- jstests/core/notablescan.js | 10 ++- jstests/core/operation_latency_histogram.js | 6 ++ jstests/core/plan_cache_clear.js | 10 ++- jstests/core/plan_cache_list_plans.js | 10 ++- jstests/core/plan_cache_list_shapes.js | 10 ++- jstests/core/plan_cache_shell_helpers.js | 10 ++- jstests/core/startup_log.js | 7 ++ jstests/core/top.js | 6 ++ jstests/core/views/views_all_commands.js | 9 ++- jstests/core/views/views_stats.js | 6 ++ .../set_read_preference_secondary.js | 88 +++++++++++++++++++++- 26 files changed, 363 insertions(+), 59 deletions(-) create mode 100644 buildscripts/resmokeconfig/suites/write_concern_majority_passthrough.yml 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( -- cgit v1.2.1