summaryrefslogtreecommitdiff
path: root/jstests/sharding
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/sharding')
-rw-r--r--jstests/sharding/agg_out_drop_database.js2
-rw-r--r--jstests/sharding/all_collection_stats.js89
-rw-r--r--jstests/sharding/all_collection_stats_auth.js91
-rw-r--r--jstests/sharding/append_oplog_note_mongos.js2
-rw-r--r--jstests/sharding/auth.js30
-rw-r--r--jstests/sharding/authCommands.js8
-rw-r--r--jstests/sharding/auto_rebalance_parallel.js42
-rw-r--r--jstests/sharding/autosplit.js10
-rw-r--r--jstests/sharding/autosplit_configure_collection.js10
-rw-r--r--jstests/sharding/autosplit_low_cardinality.js10
-rw-r--r--jstests/sharding/awaitable_hello_primary_failures.js23
-rw-r--r--jstests/sharding/balancer_collection_status.js5
-rw-r--r--jstests/sharding/balancer_window.js26
-rw-r--r--jstests/sharding/balancing_based_on_size.js94
-rw-r--r--jstests/sharding/balancing_sessions_collection_legacy.js (renamed from jstests/sharding/balancing_sessions_collection.js)9
-rw-r--r--jstests/sharding/basic_split.js4
-rw-r--r--jstests/sharding/catalog_cache_refresh_counters.js114
-rw-r--r--jstests/sharding/change_stream_no_drop.js62
-rw-r--r--jstests/sharding/clone_catalog_data.js14
-rw-r--r--jstests/sharding/cluster_time_across_add_shard.js166
-rw-r--r--jstests/sharding/collection_uuid_shard_capped_collection.js45
-rw-r--r--jstests/sharding/compound_hashed_shard_key_targeting.js5
-rw-r--r--jstests/sharding/data_size_aware_balancing_sessions_collection.js197
-rw-r--r--jstests/sharding/database_versioning_all_commands.js9
-rw-r--r--jstests/sharding/documents_db_not_exist.js48
-rw-r--r--jstests/sharding/documents_sharded.js160
-rw-r--r--jstests/sharding/drop_collection.js70
-rw-r--r--jstests/sharding/drop_collection_if_uuid_not_matching.js4
-rw-r--r--jstests/sharding/enforce_zone_policy.js54
-rw-r--r--jstests/sharding/error_propagation.js2
-rw-r--r--jstests/sharding/exhaust_hello_topology_changes.js4
-rw-r--r--jstests/sharding/extract_shard_key_values.js8
-rw-r--r--jstests/sharding/findandmodify_autosplit.js10
-rw-r--r--jstests/sharding/hidden_index.js86
-rw-r--r--jstests/sharding/implicit_create_collection_triggered_by_DDLs.js56
-rw-r--r--jstests/sharding/index_operations_abort_concurrent_outgoing_migrations.js32
-rw-r--r--jstests/sharding/internal_txns/incomplete_transaction_history_during_migration.js89
-rw-r--r--jstests/sharding/internal_txns/libs/chunk_migration_test.js4
-rw-r--r--jstests/sharding/internal_txns/libs/fixture_helpers.js23
-rw-r--r--jstests/sharding/internal_txns/libs/retryable_internal_transaction_test.js10
-rw-r--r--jstests/sharding/internal_txns/retryable_findAndModify_commit_and_abort_prepared_txns_after_failover_and_restart.js10
-rw-r--r--jstests/sharding/jumbo1.js46
-rw-r--r--jstests/sharding/jumbo_chunks.js178
-rw-r--r--jstests/sharding/libs/defragmentation_util.js26
-rw-r--r--jstests/sharding/libs/last_lts_mongod_commands.js4
-rw-r--r--jstests/sharding/libs/last_lts_mongos_commands.js5
-rw-r--r--jstests/sharding/libs/mongos_api_params_util.js7
-rw-r--r--jstests/sharding/libs/resharding_test_fixture.js100
-rw-r--r--jstests/sharding/libs/with_partial_shard_key_util.js28
-rw-r--r--jstests/sharding/log_remote_op_wait.js24
-rw-r--r--jstests/sharding/log_remote_op_wait_for_other_commands.js89
-rw-r--r--jstests/sharding/max_time_ms_does_not_leak_shard_cursor.js75
-rw-r--r--jstests/sharding/merge_let_params_size_estimation.js155
-rw-r--r--jstests/sharding/merge_with_drop_shard.js2
-rw-r--r--jstests/sharding/migrateBig.js11
-rw-r--r--jstests/sharding/migrateBig_balancer.js64
-rw-r--r--jstests/sharding/min_max_key.js28
-rw-r--r--jstests/sharding/mongos_validate_writes.js4
-rw-r--r--jstests/sharding/move_chunk_allowMigrations.js29
-rw-r--r--jstests/sharding/move_chunk_concurrent_cloning.js115
-rw-r--r--jstests/sharding/move_chunk_deferred_lookup.js101
-rw-r--r--jstests/sharding/move_chunk_interrupt_postimage.js42
-rw-r--r--jstests/sharding/move_chunk_permitMigrations.js29
-rw-r--r--jstests/sharding/move_primary_clone_test.js7
-rw-r--r--jstests/sharding/move_range_basic.js1
-rw-r--r--jstests/sharding/mr_single_reduce_split.js55
-rw-r--r--jstests/sharding/multi_writes_with_shard_version_ignored_dont_bubble_up_critical_section.js150
-rw-r--r--jstests/sharding/prefix_shard_key.js1
-rw-r--r--jstests/sharding/prepare_transaction_then_migrate.js223
-rw-r--r--jstests/sharding/presplit.js47
-rw-r--r--jstests/sharding/query/agg_explain_fmt.js1
-rw-r--r--jstests/sharding/query/delete_with_partial_shard_key.js65
-rw-r--r--jstests/sharding/query/find_and_modify_with_partial_shard_key.js55
-rw-r--r--jstests/sharding/query/lookup_graph_lookup_foreign_becomes_sharded.js6
-rw-r--r--jstests/sharding/query/lookup_mongod_unaware.js62
-rw-r--r--jstests/sharding/query/merge_write_concern.js28
-rw-r--r--jstests/sharding/query/metadata_removal.js46
-rw-r--r--jstests/sharding/query/shard_refuses_cursor_ownership.js83
-rw-r--r--jstests/sharding/read_write_concern_defaults_application.js15
-rw-r--r--jstests/sharding/refine_collection_shard_key_basic.js10
-rw-r--r--jstests/sharding/rename.js47
-rw-r--r--jstests/sharding/rename_sharded.js47
-rw-r--r--jstests/sharding/replication_with_undefined_shard_key.js4
-rw-r--r--jstests/sharding/resharding_abort_command.js2
-rw-r--r--jstests/sharding/resharding_abort_in_preparing_to_donate.js21
-rw-r--r--jstests/sharding/resharding_coordinator_recovers_abort_decision.js47
-rw-r--r--jstests/sharding/resharding_feature_flagging.js2
-rw-r--r--jstests/sharding/resharding_interrupt_before_create_state_machine.js46
-rw-r--r--jstests/sharding/resharding_large_number_of_initial_chunks.js51
-rw-r--r--jstests/sharding/resharding_metrics.js3
-rw-r--r--jstests/sharding/resharding_nonblocking_coordinator_rebuild.js14
-rw-r--r--jstests/sharding/resharding_prohibited_commands.js19
-rw-r--r--jstests/sharding/resharding_temp_ns_routing_info_unsharded.js49
-rw-r--r--jstests/sharding/resharding_update_tag_zones.js53
-rw-r--r--jstests/sharding/resharding_update_tag_zones_large.js133
-rw-r--r--jstests/sharding/run_restore_unsharded.js67
-rw-r--r--jstests/sharding/safe_secondary_reads_drop_recreate.js8
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js8
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js8
-rw-r--r--jstests/sharding/server_status_crud_metrics.js29
-rw-r--r--jstests/sharding/shard_collection_basic.js4
-rw-r--r--jstests/sharding/shard_drain_works_with_chunks_of_any_size.js92
-rw-r--r--jstests/sharding/shard_existing.js45
-rw-r--r--jstests/sharding/shard_existing_coll_chunk_count.js177
-rw-r--r--jstests/sharding/shard_keys_with_dollar_sign.js75
-rw-r--r--jstests/sharding/sharded_data_distribution.js188
-rw-r--r--jstests/sharding/sharded_data_distribution_auth.js81
-rw-r--r--jstests/sharding/sharding_balance1.js49
-rw-r--r--jstests/sharding/sharding_balance2.js9
-rw-r--r--jstests/sharding/sharding_balance3.js8
-rw-r--r--jstests/sharding/sharding_balance4.js180
-rw-r--r--jstests/sharding/sharding_migrate_cursor1.js8
-rw-r--r--jstests/sharding/sharding_statistics_server_status.js28
-rw-r--r--jstests/sharding/stale_mongos_updates_and_removes.js10
-rw-r--r--jstests/sharding/standalone_in_queryable_backup_mode.js99
-rw-r--r--jstests/sharding/timeseries_cluster_collstats.js127
-rw-r--r--jstests/sharding/timeseries_cluster_indexstats.js8
-rw-r--r--jstests/sharding/timeseries_coll_mod.js9
-rw-r--r--jstests/sharding/timeseries_query.js2
-rw-r--r--jstests/sharding/timeseries_sharding_admin_commands.js12
-rw-r--r--jstests/sharding/timeseries_update.js115
-rw-r--r--jstests/sharding/top_chunk_autosplit.js20
-rw-r--r--jstests/sharding/transfer_mods_large_batches.js132
-rw-r--r--jstests/sharding/ttl_deletes_not_targeting_orphaned_documents.js7
-rw-r--r--jstests/sharding/update_delete_many_metrics.js163
-rw-r--r--jstests/sharding/write_cmd_auto_split.js10
-rw-r--r--jstests/sharding/zone_changes_hashed.js26
-rw-r--r--jstests/sharding/zone_changes_range.js15
128 files changed, 4722 insertions, 1389 deletions
diff --git a/jstests/sharding/agg_out_drop_database.js b/jstests/sharding/agg_out_drop_database.js
index a911278cc84..7cceb60e848 100644
--- a/jstests/sharding/agg_out_drop_database.js
+++ b/jstests/sharding/agg_out_drop_database.js
@@ -3,7 +3,7 @@
*
* @tags: [
* requires_fcv_51,
- * does_not_support_stepdowns, // DropDatabaseCoordinator drops the input collection on step-up
+ * does_not_support_stepdowns, # DropDatabaseCoordinator drops the input collection on step-up
* ]
*/
diff --git a/jstests/sharding/all_collection_stats.js b/jstests/sharding/all_collection_stats.js
new file mode 100644
index 00000000000..2fd89236e70
--- /dev/null
+++ b/jstests/sharding/all_collection_stats.js
@@ -0,0 +1,89 @@
+/*
+ * Test to validate the $_internalAllCollectionStats stage for storageStats.
+ *
+ * @tags: [
+ * requires_fcv_60,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+// Configure initial sharding cluster
+const st = new ShardingTest({shards: 2});
+const mongos = st.s;
+
+const dbName = "test";
+const testDb = mongos.getDB(dbName);
+const adminDb = mongos.getDB("admin");
+
+// Insert sharded collections to validate the aggregation stage
+for (let i = 0; i < 10; i++) {
+ const coll = "coll" + i;
+ assert(st.adminCommand({shardcollection: dbName + "." + coll, key: {skey: 1}}));
+ assert.commandWorked(testDb.getCollection(coll).insert({skey: i}));
+}
+
+// Insert some unsharded collections to validate the aggregation stage
+for (let i = 10; i < 20; i++) {
+ const coll = "coll" + i;
+ assert.commandWorked(testDb.getCollection(coll).insert({skey: i}));
+}
+
+// Get output data
+const outputData =
+ adminDb.aggregate([{$_internalAllCollectionStats: {stats: {storageStats: {}}}}]).toArray();
+assert.gte(outputData.length, 20);
+
+// Testing for comparing each collection returned from $_internalAllCollectionStats to $collStats
+for (let i = 0; i < 20; i++) {
+ const coll = "coll" + i;
+ const expectedResults =
+ testDb.getCollection(coll).aggregate([{$collStats: {storageStats: {}}}]).toArray();
+ assert.neq(null, expectedResults);
+ assert.eq(expectedResults.length, 1);
+
+ let exists = false;
+ for (const data of outputData) {
+ const ns = data.ns;
+ if (dbName + "." + coll === ns) {
+ assert.eq(data.host, expectedResults[0].host);
+ assert.eq(data.shard, expectedResults[0].shard);
+ assert.eq(data.storageStats.size, expectedResults[0].storageStats.size);
+ assert.eq(data.storageStats.count, expectedResults[0].storageStats.count);
+ assert.eq(data.storageStats.avgObjSize, expectedResults[0].storageStats.avgObjSize);
+ assert.eq(data.storageStats.storageSize, expectedResults[0].storageStats.storageSize);
+ assert.eq(data.storageStats.freeStorageSize,
+ expectedResults[0].storageStats.freeStorageSize);
+ assert.eq(data.storageStats.nindexes, expectedResults[0].storageStats.nindexes);
+ assert.eq(data.storageStats.totalIndexSize,
+ expectedResults[0].storageStats.totalIndexSize);
+ assert.eq(data.storageStats.totalSize, expectedResults[0].storageStats.totalSize);
+ exists = true;
+ break;
+ }
+ }
+
+ assert(exists);
+}
+
+// Test valid query with empty specification
+assert.commandWorked(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$_internalAllCollectionStats: {}}], cursor: {}}));
+
+// Test invalid queries/values.
+assert.commandFailedWithCode(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$_internalAllCollectionStats: 3}], cursor: {}}),
+ 6789103);
+
+const response = assert.commandFailedWithCode(testDb.runCommand({
+ aggregate: "foo",
+ pipeline: [{$_internalAllCollectionStats: {stats: {storageStats: {}}}}],
+ cursor: {}
+}),
+ 6789104);
+assert.neq(-1, response.errmsg.indexOf("$_internalAllCollectionStats"), response.errmsg);
+assert.neq(-1, response.errmsg.indexOf("admin database"), response.errmsg);
+
+st.stop();
+})();
diff --git a/jstests/sharding/all_collection_stats_auth.js b/jstests/sharding/all_collection_stats_auth.js
new file mode 100644
index 00000000000..022bc88ab4a
--- /dev/null
+++ b/jstests/sharding/all_collection_stats_auth.js
@@ -0,0 +1,91 @@
+/*
+ * Test to validate the privileges of using $_internalAllCollectionStats stage.
+ *
+ * @tags: [
+ * requires_fcv_60,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+if (!TestData.auth) {
+ jsTestLog("Skipping testing authorization since auth is not enabled");
+ return;
+}
+
+// Test privileges
+function testPrivileges() {
+ // Create new role with the exact privileges to execute $allCollectionStats
+ assert.commandWorked(adminDb.runCommand({
+ createRole: "role_ok_priv",
+ roles: [],
+ privileges: [{resource: {cluster: true}, actions: ["allCollectionStats"]}]
+ }));
+
+ // Creates users with privileges and no privileges
+ assert.commandWorked(adminDb.runCommand({createUser: "user_no_priv", pwd: "pwd", roles: []}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv1", pwd: "pwd", roles: [{role: "role_ok_priv", db: 'admin'}]}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv2", pwd: "pwd", roles: [{role: "clusterMonitor", db: 'admin'}]}));
+
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv1", "pwd"));
+ assert.commandWorked(adminDb.runCommand({
+ aggregate: 1,
+ pipeline: [{$_internalAllCollectionStats: {stats: {storageStats: {}}}}],
+ cursor: {}
+ }));
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv2", "pwd"));
+ assert.commandWorked(adminDb.runCommand({
+ aggregate: 1,
+ pipeline: [{$_internalAllCollectionStats: {stats: {storageStats: {}}}}],
+ cursor: {}
+ }));
+ assert(adminDb.logout());
+
+ // User has no privileges to execute the stage
+ assert(adminDb.auth("user_no_priv", "pwd"));
+ assert.commandFailedWithCode(
+ adminDb.runCommand({
+ aggregate: 1,
+ pipeline: [{$_internalAllCollectionStats: {stats: {storageStats: {}}}}],
+ cursor: {}
+ }),
+ ErrorCodes.Unauthorized,
+ "user should no longer have privileges to execute $_internalAllCollectionStats stage.");
+ assert(adminDb.logout());
+}
+
+// Configure initial sharding cluster
+const st = new ShardingTest({shards: 1});
+const mongos = st.s;
+
+const ns1 = "test.foo";
+const adminDb = mongos.getDB("admin");
+const testDb = mongos.getDB("test");
+
+// Create a super user with __system role.
+assert.commandWorked(adminDb.runCommand({createUser: "super", pwd: "super", roles: ["__system"]}));
+assert(adminDb.logout());
+assert(adminDb.auth("super", "super"));
+
+st.adminCommand({shardcollection: ns1, key: {skey: 1}});
+
+// Insert data to validate the aggregation stage
+for (let i = 0; i < 6; i++) {
+ assert.commandWorked(testDb.getCollection("foo").insert({skey: i}));
+}
+
+testPrivileges();
+
+st.stop();
+})();
diff --git a/jstests/sharding/append_oplog_note_mongos.js b/jstests/sharding/append_oplog_note_mongos.js
index cc19980c5b1..1369ecb06be 100644
--- a/jstests/sharding/append_oplog_note_mongos.js
+++ b/jstests/sharding/append_oplog_note_mongos.js
@@ -1,8 +1,6 @@
/**
* Tests that the 'appendOplogNote' command on mongos correctly performs a no-op write on each
* shard and advances the $clusterTime.
- *
- * @tags: [requires_fcv_60]
*/
(function() {
diff --git a/jstests/sharding/auth.js b/jstests/sharding/auth.js
index 48351d0a59b..3bf15b56716 100644
--- a/jstests/sharding/auth.js
+++ b/jstests/sharding/auth.js
@@ -10,6 +10,7 @@
'use strict';
load("jstests/replsets/rslib.js");
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js");
// Replica set nodes started with --shardsvr do not enable key generation until they are added
// to a sharded cluster and reject commands with gossiped clusterTime from users without the
@@ -174,25 +175,32 @@ awaitRSClientHosts(s.s, d2.nodes, {ok: true});
s.getDB("test").foo.remove({});
-var num = 10000;
+var num = 10;
assert.commandWorked(s.s.adminCommand({split: "test.foo", middle: {x: num / 2}}));
+const bigString = 'X'.repeat(1024 * 1024); // 1MB
var bulk = s.getDB("test").foo.initializeUnorderedBulkOp();
for (i = 0; i < num; i++) {
- bulk.insert({_id: i, x: i, abc: "defg", date: new Date(), str: "all the talk on the market"});
+ bulk.insert({_id: i, x: i, abc: "defg", date: new Date(), str: bigString});
}
assert.commandWorked(bulk.execute());
s.startBalancer(60000);
-assert.soon(function() {
- var d1Chunks = findChunksUtil.countChunksForNs(s.getDB("config"), 'test.foo', {shard: "d1"});
- var d2Chunks = findChunksUtil.countChunksForNs(s.getDB("config"), 'test.foo', {shard: "d2"});
- var totalChunks = findChunksUtil.countChunksForNs(s.getDB("config"), 'test.foo');
+const balanceAccordingToDataSize =
+ FeatureFlagUtil.isEnabled(s.getDB('admin'), "BalanceAccordingToDataSize");
+if (!balanceAccordingToDataSize) {
+ assert.soon(function() {
+ var d1Chunks =
+ findChunksUtil.countChunksForNs(s.getDB("config"), 'test.foo', {shard: "d1"});
+ var d2Chunks =
+ findChunksUtil.countChunksForNs(s.getDB("config"), 'test.foo', {shard: "d2"});
+ var totalChunks = findChunksUtil.countChunksForNs(s.getDB("config"), 'test.foo');
- print("chunks: " + d1Chunks + " " + d2Chunks + " " + totalChunks);
+ print("chunks: " + d1Chunks + " " + d2Chunks + " " + totalChunks);
- return d1Chunks > 0 && d2Chunks > 0 && (d1Chunks + d2Chunks == totalChunks);
-}, "Chunks failed to balance", 60000, 5000);
+ return d1Chunks > 0 && d2Chunks > 0 && (d1Chunks + d2Chunks == totalChunks);
+ }, "Chunks failed to balance", 60000, 5000);
+}
// SERVER-33753: count() without predicate can be wrong on sharded collections.
// assert.eq(s.getDB("test").foo.count(), num+1);
@@ -234,7 +242,7 @@ if (numDocs != num) {
// This call also waits for any ongoing balancing to stop
s.stopBalancer(60000);
-var cursor = s.getDB("test").foo.find({x: {$lt: 500}});
+var cursor = s.getDB("test").foo.find({x: {$lt: 5}});
var count = 0;
while (cursor.hasNext()) {
@@ -242,7 +250,7 @@ while (cursor.hasNext()) {
count++;
}
-assert.eq(count, 500);
+assert.eq(count, 5);
logout(adminUser);
diff --git a/jstests/sharding/authCommands.js b/jstests/sharding/authCommands.js
index 7d78366b99e..d1059db00d1 100644
--- a/jstests/sharding/authCommands.js
+++ b/jstests/sharding/authCommands.js
@@ -88,12 +88,8 @@ st.startBalancer();
// Make sure we've done at least some splitting, so the balancer will work
assert.gt(findChunksUtil.findChunksByNs(configDB, 'test.foo').count(), 2);
-// Make sure we eventually balance all the chunks we've created
-assert.soon(function() {
- var x = st.chunkDiff("foo", "test");
- print("chunk diff: " + x);
- return x < 2 && configDB.locks.findOne({_id: 'test.foo'}).state == 0;
-}, "no balance happened", 15 * 60 * 1000);
+// Make sure we eventually balance the 'test.foo' collection
+st.awaitBalance('foo', 'test', 60 * 5 * 1000);
var map = function() {
emit(this.i, this.j);
diff --git a/jstests/sharding/auto_rebalance_parallel.js b/jstests/sharding/auto_rebalance_parallel.js
index 1e55e8cbc11..d9f2f00715e 100644
--- a/jstests/sharding/auto_rebalance_parallel.js
+++ b/jstests/sharding/auto_rebalance_parallel.js
@@ -1,5 +1,8 @@
/**
* Tests that the cluster is balanced in parallel in one balancer round (standalone).
+ * @tags: [
+ * requires_fcv_60,
+ * ]
*/
(function() {
@@ -7,7 +10,7 @@
load("jstests/sharding/libs/find_chunks_util.js");
-var st = new ShardingTest({shards: 4});
+const st = new ShardingTest({shards: 4, other: {chunkSize: 1, enableAutoSplitter: false}});
var config = st.s0.getDB('config');
assert.commandWorked(st.s0.adminCommand({enableSharding: 'TestDB'}));
@@ -18,35 +21,34 @@ function prepareCollectionForBalance(collName) {
var coll = st.s0.getCollection(collName);
- // Create 4 chunks initially and ensure they get balanced within 1 balancer round
- assert.commandWorked(coll.insert({Key: 1, Value: 'Test value 1'}));
- assert.commandWorked(coll.insert({Key: 10, Value: 'Test value 10'}));
- assert.commandWorked(coll.insert({Key: 20, Value: 'Test value 20'}));
- assert.commandWorked(coll.insert({Key: 30, Value: 'Test value 30'}));
+ const bigString = 'X'.repeat(1024 * 1024); // 1MB
+ // Create 6 chunks initially and ensure they get balanced within 1 balancer round
+ assert.commandWorked(coll.insert({Key: 1, Value: 'Test value 1', s: bigString}));
+ assert.commandWorked(coll.insert({Key: 10, Value: 'Test value 10', s: bigString}));
+ assert.commandWorked(coll.insert({Key: 20, Value: 'Test value 20', s: bigString}));
+ assert.commandWorked(coll.insert({Key: 30, Value: 'Test value 30', s: bigString}));
+ assert.commandWorked(coll.insert({Key: 40, Value: 'Test value 40', s: bigString}));
+ assert.commandWorked(coll.insert({Key: 50, Value: 'Test value 50', s: bigString}));
assert.commandWorked(st.splitAt(collName, {Key: 10}));
assert.commandWorked(st.splitAt(collName, {Key: 20}));
assert.commandWorked(st.splitAt(collName, {Key: 30}));
+ assert.commandWorked(st.splitAt(collName, {Key: 40}));
+ assert.commandWorked(st.splitAt(collName, {Key: 50}));
- // Move two of the chunks to st.shard1.shardName so we have option to do parallel balancing
- assert.commandWorked(st.moveChunk(collName, {Key: 20}, st.shard1.shardName));
+ // Move 3 of the chunks to st.shard1.shardName so we have option to do parallel balancing
assert.commandWorked(st.moveChunk(collName, {Key: 30}, st.shard1.shardName));
+ assert.commandWorked(st.moveChunk(collName, {Key: 40}, st.shard1.shardName));
+ assert.commandWorked(st.moveChunk(collName, {Key: 50}, st.shard1.shardName));
assert.eq(
- 2, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard0.shardName}).itcount());
+ 3, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard0.shardName}).itcount());
assert.eq(
- 2, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard1.shardName}).itcount());
+ 3, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard1.shardName}).itcount());
}
function checkCollectionBalanced(collName) {
- assert.eq(
- 1, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard0.shardName}).itcount());
- assert.eq(
- 1, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard1.shardName}).itcount());
- assert.eq(
- 1, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard2.shardName}).itcount());
- assert.eq(
- 1, findChunksUtil.findChunksByNs(config, collName, {shard: st.shard3.shardName}).itcount());
+ st.verifyCollectionIsBalanced(st.s.getCollection(collName));
}
function countMoves(collName) {
@@ -62,8 +64,8 @@ const testColl1InitialMoves = countMoves('TestDB.TestColl1');
const testColl2InitialMoves = countMoves('TestDB.TestColl2');
st.startBalancer();
-st.waitForBalancer(true, 60000);
-st.waitForBalancer(true, 60000);
+st.awaitBalance("TestColl1", "TestDB");
+st.awaitBalance("TestColl2", "TestDB");
st.stopBalancer();
checkCollectionBalanced('TestDB.TestColl1');
diff --git a/jstests/sharding/autosplit.js b/jstests/sharding/autosplit.js
index af67d7820cb..e2242b45bcb 100644
--- a/jstests/sharding/autosplit.js
+++ b/jstests/sharding/autosplit.js
@@ -5,6 +5,7 @@
'use strict';
load('jstests/sharding/autosplit_include.js');
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js"); // for FeatureFlagUtil.isEnabled
var s = new ShardingTest({
name: "auto1",
@@ -13,6 +14,15 @@ var s = new ShardingTest({
other: {enableAutoSplit: true, chunkSize: 10},
});
+// TODO SERVER-66652 remove this test after 7.0 branches out
+const noMoreAutoSplitterFeatureFlag =
+ FeatureFlagUtil.isEnabled(s.configRS.getPrimary().getDB('admin'), "NoMoreAutoSplitter");
+if (noMoreAutoSplitterFeatureFlag) {
+ jsTestLog("Skipping as featureFlagNoMoreAutosplitter is enabled");
+ s.stop();
+ return;
+}
+
assert.commandWorked(s.s0.adminCommand({enablesharding: "test"}));
s.ensurePrimaryShard('test', s.shard1.shardName);
assert.commandWorked(s.s0.adminCommand({shardcollection: "test.foo", key: {num: 1}}));
diff --git a/jstests/sharding/autosplit_configure_collection.js b/jstests/sharding/autosplit_configure_collection.js
index c1658add12f..83acde55afd 100644
--- a/jstests/sharding/autosplit_configure_collection.js
+++ b/jstests/sharding/autosplit_configure_collection.js
@@ -11,6 +11,7 @@
'use strict';
load('jstests/sharding/autosplit_include.js');
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js"); // for FeatureFlagUtil.isEnabled
let st = new ShardingTest({
name: "auto1",
@@ -19,6 +20,15 @@ let st = new ShardingTest({
other: {enableAutoSplit: true},
});
+// TODO SERVER-66652 remove this test after 7.0 branches out
+const noMoreAutoSplitterFeatureFlag =
+ FeatureFlagUtil.isEnabled(st.configRS.getPrimary().getDB('admin'), "NoMoreAutoSplitter");
+if (noMoreAutoSplitterFeatureFlag) {
+ jsTestLog("Skipping as featureFlagNoMoreAutosplitter is enabled");
+ st.stop();
+ return;
+}
+
const fullNS = "test.foo";
const bigString = "X".repeat(1024 * 1024 / 16); // 65 KB
diff --git a/jstests/sharding/autosplit_low_cardinality.js b/jstests/sharding/autosplit_low_cardinality.js
index 1f944fc2140..d6abe0d807b 100644
--- a/jstests/sharding/autosplit_low_cardinality.js
+++ b/jstests/sharding/autosplit_low_cardinality.js
@@ -8,12 +8,22 @@
'use strict';
load('jstests/sharding/autosplit_include.js');
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js"); // for FeatureFlagUtil.isEnabled
var st = new ShardingTest({
name: "low_cardinality",
other: {enableAutoSplit: true, chunkSize: 1},
});
+// TODO SERVER-66652 remove this test after 7.0 branches out
+const noMoreAutoSplitterFeatureFlag =
+ FeatureFlagUtil.isEnabled(st.configRS.getPrimary().getDB('admin'), "NoMoreAutoSplitter");
+if (noMoreAutoSplitterFeatureFlag) {
+ jsTestLog("Skipping as featureFlagNoMoreAutosplitter is enabled");
+ st.stop();
+ return;
+}
+
assert.commandWorked(st.s.adminCommand({enablesharding: "test"}));
assert.commandWorked(st.s.adminCommand({shardcollection: "test.foo", key: {sk: 1}}));
diff --git a/jstests/sharding/awaitable_hello_primary_failures.js b/jstests/sharding/awaitable_hello_primary_failures.js
index b6a387920c3..1522f755347 100644
--- a/jstests/sharding/awaitable_hello_primary_failures.js
+++ b/jstests/sharding/awaitable_hello_primary_failures.js
@@ -22,9 +22,9 @@ let rsPrimary = st.rs0.getPrimary();
// Make sure mongos knows who the primary is
awaitRSClientHosts(mongos, {host: rsPrimary.name}, {ok: true, ismaster: true});
-// Turn on the waitInHello failpoint. This will cause the primary node to cease sending isMaster
+// Turn on the waitInHello failpoint. This will cause the primary node to cease sending "hello"
// responses and the RSM should mark the node as down
-jsTestLog("Turning on waitInHello failpoint. Node should stop sending isMaster responses.");
+jsTestLog("Turning on waitInHello failpoint. Node should stop sending hello responses.");
const helloFailpoint = configureFailPoint(rsPrimary, "waitInHello");
awaitRSClientHosts(mongos, {host: rsPrimary.name}, {ok: false, ismaster: false});
helloFailpoint.off();
@@ -32,25 +32,26 @@ helloFailpoint.off();
// Wait for mongos to find out the node is still primary
awaitRSClientHosts(mongos, {host: rsPrimary.name}, {ok: true, ismaster: true});
-// Force the primary node to fail all isMaster requests. The RSM should mark the node as down.
-jsTestLog("Turning on failCommand failpoint. Node should fail all isMaster responses.");
-const failCmdFailpoint = configureFailPoint(
- rsPrimary,
- "failCommand",
- {errorCode: ErrorCodes.CommandFailed, failCommands: ["isMaster"], failInternalCommands: true});
+// Force the primary node to fail all "hello" requests. The RSM should mark the node as down.
+jsTestLog("Turning on failCommand failpoint. Node should fail all hello/isMaster responses.");
+const failCmdFailpoint = configureFailPoint(rsPrimary, "failCommand", {
+ errorCode: ErrorCodes.CommandFailed,
+ failCommands: ["hello", "isMaster"],
+ failInternalCommands: true
+});
awaitRSClientHosts(mongos, {host: rsPrimary.name}, {ok: false, ismaster: false});
failCmdFailpoint.off();
// Wait for mongos to find out the node is still primary
awaitRSClientHosts(mongos, {host: rsPrimary.name}, {ok: true, ismaster: true});
-// Force the primary node to end the isMaster stream by not setting the 'moreToCome' bit on the
+// Force the primary node to end the "hello" stream by not setting the 'moreToCome' bit on the
// resposne. The RSM should not mark the server as down or unknown and should continue monitoring
// the node.
jsTestLog(
- "Turning on doNotSetMoreToCome failpoint. Node should return successful isMaster responses.");
+ "Turning on doNotSetMoreToCome failpoint. Node should return successful hello responses.");
const moreToComeFailpoint = configureFailPoint(rsPrimary, "doNotSetMoreToCome");
-// Wait for maxAwaitTimeMS to guarantee that mongos has received at least one isMaster response from
+// Wait for maxAwaitTimeMS to guarantee that mongos has received at least one "hello" response from
// the primary without the moreToCome bit set.
sleep(10000);
awaitRSClientHosts(mongos, {host: rsPrimary.name}, {ok: true, ismaster: true});
diff --git a/jstests/sharding/balancer_collection_status.js b/jstests/sharding/balancer_collection_status.js
index 25622820605..e2bb502dc04 100644
--- a/jstests/sharding/balancer_collection_status.js
+++ b/jstests/sharding/balancer_collection_status.js
@@ -6,7 +6,6 @@
'use strict';
const chunkSizeMB = 1;
-
let st = new ShardingTest({
shards: 3,
other: {
@@ -55,6 +54,10 @@ assert.eq(result.balancerCompliant, true);
// get shardIds
const shards = st.s0.getDB('config').shards.find().toArray();
+const bigString = 'X'.repeat(1024 * 1024); // 1MB
+for (var i = 0; i < 30; i += 10) {
+ assert.commandWorked(st.s0.getDB('db').getCollection('col').insert({key: i, s: bigString}));
+}
// manually split and place the 3 chunks on the same shard
assert.commandWorked(st.s0.adminCommand({split: 'db.col', middle: {key: 10}}));
assert.commandWorked(st.s0.adminCommand({split: 'db.col', middle: {key: 20}}));
diff --git a/jstests/sharding/balancer_window.js b/jstests/sharding/balancer_window.js
index ee48db64844..0e93d967363 100644
--- a/jstests/sharding/balancer_window.js
+++ b/jstests/sharding/balancer_window.js
@@ -45,17 +45,23 @@ var HourAndMinute = function(hour, minutes) {
};
};
-var st = new ShardingTest({shards: 2});
-var configDB = st.s.getDB('config');
-assert.commandWorked(configDB.adminCommand({enableSharding: 'test'}));
-assert.commandWorked(configDB.adminCommand({shardCollection: 'test.user', key: {_id: 1}}));
+const st = new ShardingTest({shards: 2, other: {chunkSize: 1, enableAutoSplit: false}});
+const dbName = 'test';
+const collName = 'user';
+const ns = dbName + '.' + collName;
+const configDB = st.s.getDB('config');
+assert.commandWorked(configDB.adminCommand({enableSharding: dbName}));
+assert.commandWorked(configDB.adminCommand({shardCollection: ns, key: {_id: 1}}));
+const bigString = 'X'.repeat(1024 * 1024); // 1MB
+const coll = st.s.getDB(dbName).getCollection(collName);
for (var x = 0; x < 150; x += 10) {
- configDB.adminCommand({split: 'test.user', middle: {_id: x}});
+ coll.insert({_id: x, s: bigString});
+ configDB.adminCommand({split: ns, middle: {_id: x}});
}
var shard0Chunks =
- findChunksUtil.findChunksByNs(configDB, 'test.user', {shard: st.shard0.shardName}).count();
+ findChunksUtil.findChunksByNs(configDB, ns, {shard: st.shard0.shardName}).count();
var startDate = new Date();
var hourMinStart = new HourAndMinute(startDate.getHours(), startDate.getMinutes());
@@ -72,10 +78,10 @@ assert.commandWorked(
true));
st.startBalancer();
-st.waitForBalancer(true, 60000);
+st.awaitBalancerRound();
var shard0ChunksAfter =
- findChunksUtil.findChunksByNs(configDB, 'test.user', {shard: st.shard0.shardName}).count();
+ findChunksUtil.findChunksByNs(configDB, ns, {shard: st.shard0.shardName}).count();
assert.eq(shard0Chunks, shard0ChunksAfter);
assert.commandWorked(configDB.settings.update(
@@ -87,10 +93,10 @@ assert.commandWorked(configDB.settings.update(
},
true));
-st.waitForBalancer(true, 60000);
+st.awaitBalancerRound();
shard0ChunksAfter =
- findChunksUtil.findChunksByNs(configDB, 'test.user', {shard: st.shard0.shardName}).count();
+ findChunksUtil.findChunksByNs(configDB, ns, {shard: st.shard0.shardName}).count();
assert.neq(shard0Chunks, shard0ChunksAfter);
st.stop();
diff --git a/jstests/sharding/balancing_based_on_size.js b/jstests/sharding/balancing_based_on_size.js
new file mode 100644
index 00000000000..60e5dec3b7f
--- /dev/null
+++ b/jstests/sharding/balancing_based_on_size.js
@@ -0,0 +1,94 @@
+/*
+ * Test that the balancer is redistributing data based on the actual amount of data
+ * for a collection on each node, converging when the size difference becomes small.
+ *
+ * @tags: [
+ * featureFlagBalanceAccordingToDataSize,
+ * requires_fcv_60,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+load("jstests/sharding/libs/find_chunks_util.js");
+
+const maxChunkSizeMB = 1;
+const st = new ShardingTest(
+ {shards: 2, mongos: 1, other: {chunkSize: maxChunkSizeMB, enableBalancer: false}});
+const dbName = 'test';
+const coll = st.getDB(dbName).getCollection('foo');
+const ns = coll.getFullName();
+const mongos = st.s;
+const shard0 = st.shard0.shardName;
+const shard1 = st.shard1.shardName;
+
+// Shard collection with one chunk on shard0 [MinKey, 0) and one chunk on shard1 [0, MinKey)
+assert.commandWorked(mongos.adminCommand({enablesharding: dbName, primaryShard: shard0}));
+assert.commandWorked(mongos.adminCommand({shardcollection: ns, key: {_id: 1}}));
+assert.commandWorked(mongos.adminCommand({split: ns, middle: {_id: 0}}));
+assert.commandWorked(mongos.adminCommand({moveChunk: ns, find: {_id: 0}, to: shard1}));
+
+const bigString = 'X'.repeat(1024 * 1024); // 1MB
+
+// Insert 10MB of documents in range [MinKey, 0) on shard0
+var bulk = coll.initializeUnorderedBulkOp();
+for (var i = -1; i > -11; i--) {
+ bulk.insert({_id: i, s: bigString});
+}
+assert.commandWorked(bulk.execute());
+
+// Insert 3MB of documents in range [0, MaxKey) on shard1
+bulk = coll.initializeUnorderedBulkOp();
+for (var i = 0; i < 3; i++) {
+ bulk.insert({_id: i, s: bigString});
+}
+assert.commandWorked(bulk.execute());
+
+// Create 3 more chunks on shard0
+assert.commandWorked(mongos.adminCommand({split: ns, middle: {_id: 1}}));
+assert.commandWorked(mongos.adminCommand({split: ns, middle: {_id: 2}}));
+assert.commandWorked(mongos.adminCommand({split: ns, middle: {_id: 3}}));
+
+// At this point, the distribution of chunks for the testing collection is the following:
+// - On shard0 (10MB):
+// { "_id" : { "$minKey" : 1 } } -->> { "_id" : 0 }
+// - On shard1 (3MB):
+// { "_id" : 0 } -->> { "_id" : 1 }
+// { "_id" : 1 } -->> { "_id" : 2 }
+// { "_id" : 2 } -->> { "_id" : 3 }
+// { "_id" : 3 } -->> { "_id" : { "$maxKey" : 1 } }
+jsTestLog("Printing sharding status before starting balancer");
+st.printShardingStatus();
+st.startBalancer();
+
+st.awaitCollectionBalance(coll, 60000 /* timeout */, 1000 /* interval */);
+const chunksBeforeNoopRound = findChunksUtil.findChunksByNs(st.config, ns).toArray();
+
+// Check that the collection is balanced
+st.verifyCollectionIsBalanced(coll);
+
+// Wait for some more rounds and then check the balancer is not wrongly moving around data
+st.forEachConfigServer((conn) => {
+ conn.adminCommand({
+ configureFailPoint: 'overrideBalanceRoundInterval',
+ mode: 'alwaysOn',
+ data: {intervalMs: 100}
+ });
+});
+
+st.awaitBalancerRound();
+st.awaitBalancerRound();
+st.awaitBalancerRound();
+
+st.stopBalancer();
+jsTestLog("Printing sharding status after stopping balancer");
+st.printShardingStatus();
+
+// Check that no move has been performed during the noop rounds (if the routing table did not
+// change, it means data are still balanced)
+const chunksAfterNoopRound = findChunksUtil.findChunksByNs(st.config, ns).toArray();
+assert.eq(chunksBeforeNoopRound, chunksAfterNoopRound);
+
+st.stop();
+})();
diff --git a/jstests/sharding/balancing_sessions_collection.js b/jstests/sharding/balancing_sessions_collection_legacy.js
index 231f633dead..230235a7a47 100644
--- a/jstests/sharding/balancing_sessions_collection.js
+++ b/jstests/sharding/balancing_sessions_collection_legacy.js
@@ -6,6 +6,7 @@
(function() {
"use strict";
+load("jstests/libs/feature_flag_util.js");
load("jstests/sharding/libs/find_chunks_util.js");
// TODO SERVER-50144 Remove this and allow orphan checking.
@@ -112,6 +113,14 @@ const st = new ShardingTest({
shards: numShards,
other: {configOptions: {setParameter: {minNumChunksForSessionsCollection: kMinNumChunks}}}
});
+
+// TODO SERVER-66782 delete this file
+if (FeatureFlagUtil.isEnabled(st.configRS.getPrimary().getDB('admin'),
+ "BalanceAccordingToDataSize")) {
+ jsTestLog("Skipping as featureFlagBalanceAccordingToDataSize is enabled");
+ st.stop();
+ return;
+}
const kSessionsNs = "config.system.sessions";
const configDB = st.s.getDB("config");
diff --git a/jstests/sharding/basic_split.js b/jstests/sharding/basic_split.js
index c4a819ae026..f401b8d0047 100644
--- a/jstests/sharding/basic_split.js
+++ b/jstests/sharding/basic_split.js
@@ -91,6 +91,10 @@ assert.neq(null, findChunksUtil.findOneChunkByNs(configDB, 'test.compound', {min
// cannot split on existing chunk boundary.
assert.commandFailed(configDB.adminCommand({split: 'test.compound', middle: {x: 0, y: 0}}));
+assert.commandFailed(
+ configDB.adminCommand({split: 'test.compound', middle: {x: MinKey, y: MinKey}}));
+assert.commandFailed(
+ configDB.adminCommand({split: 'test.compound', middle: {x: MaxKey, y: MaxKey}}));
bulk = testDB.compound.initializeUnorderedBulkOp();
for (x = -1200; x < 1200; x++) {
diff --git a/jstests/sharding/catalog_cache_refresh_counters.js b/jstests/sharding/catalog_cache_refresh_counters.js
deleted file mode 100644
index 028ef2f2c59..00000000000
--- a/jstests/sharding/catalog_cache_refresh_counters.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * Test that operations that get blocked by catalog cache refreshes get logged to
- * shardingStatistics.
- */
-
-(function() {
-'use strict';
-
-load('jstests/sharding/libs/sharded_transactions_helpers.js');
-load("jstests/sharding/libs/chunk_bounds_util.js");
-load("jstests/sharding/libs/find_chunks_util.js");
-
-let st = new ShardingTest({mongos: 2, shards: 2});
-const configDB = st.s.getDB('config');
-const dbName = "test";
-const collName = "foo";
-const ns = dbName + "." + collName;
-const mongos0DB = st.s0.getDB(dbName);
-const mongos1DB = st.s1.getDB(dbName);
-const mongos0Coll = mongos0DB[collName];
-const mongos1Coll = mongos1DB[collName];
-
-let setUp = () => {
- /**
- * Sets up a test by moving chunks to such that one chunk is on each
- * shard, with the following distribution:
- * shard0: [-inf, 0)
- * shard1: [0, inf)
- */
- assert.commandWorked(st.s0.adminCommand({enableSharding: dbName}));
- assert.commandWorked(st.s0.adminCommand({movePrimary: dbName, to: st.shard0.shardName}));
- assert.commandWorked(st.s0.adminCommand({shardCollection: ns, key: {x: 1}}));
- assert.commandWorked(st.s0.adminCommand({split: ns, middle: {x: 0}}));
-
- flushRoutersAndRefreshShardMetadata(st, {ns});
-};
-
-let getOpsBlockedByRefresh = () => {
- return assert.commandWorked(st.s1.adminCommand({serverStatus: 1}))
- .shardingStatistics.catalogCache.operationsBlockedByRefresh;
-};
-
-let verifyBlockedOperationsChange = (oldOperationsCount, increasedOps) => {
- let newOperationsCount = getOpsBlockedByRefresh();
- increasedOps.forEach(op => {
- assert.eq(newOperationsCount[op.opType], oldOperationsCount[op.opType] + op.increase);
- });
-};
-
-let getShardToTargetForMoveChunk = () => {
- const chunkDocs = findChunksUtil.findChunksByNs(configDB, ns).toArray();
- const shardChunkBounds = chunkBoundsUtil.findShardChunkBounds(chunkDocs);
- const shardThatOwnsChunk = chunkBoundsUtil.findShardForShardKey(st, shardChunkBounds, {x: 100});
- return st.getOther(shardThatOwnsChunk).shardName;
-};
-
-let runTest = (operationToRunFn, expectedOpIncreases) => {
- let opsBlockedByRefresh = getOpsBlockedByRefresh();
-
- // Move chunk to ensure stale shard version for the next operation.
- assert.commandWorked(
- st.s0.adminCommand({moveChunk: ns, find: {x: 100}, to: getShardToTargetForMoveChunk()}));
-
- operationToRunFn();
- verifyBlockedOperationsChange(opsBlockedByRefresh, expectedOpIncreases);
-};
-
-setUp();
-
-/**
- * Verify that insert operations get logged when blocked by a refresh.
- */
-runTest(() => assert.commandWorked(mongos1Coll.insert({x: 250})), [
- {opType: 'countAllOperations', increase: 2},
- {opType: 'countInserts', increase: 1},
- {opType: 'countCommands', increase: 1}
-]);
-
-/**
- * Verify that queries get logged when blocked by a refresh.
- */
-runTest(() => mongos1Coll.findOne({x: 250}),
- [{opType: 'countAllOperations', increase: 1}, {opType: 'countQueries', increase: 1}]);
-
-/**
- * Verify that updates get logged when blocked by a refresh.
- */
-runTest(() => assert.commandWorked(mongos1Coll.update({x: 250}, {$set: {a: 1}})), [
- {opType: 'countAllOperations', increase: 2},
- {opType: 'countUpdates', increase: 1},
- {opType: 'countCommands', increase: 1}
-]);
-
-/**
- * Verify that deletes get logged when blocked by a refresh.
- */
-runTest(() => assert.commandWorked(mongos1Coll.remove({x: 250})), [
- {opType: 'countAllOperations', increase: 2},
- {opType: 'countDeletes', increase: 1},
- {opType: 'countCommands', increase: 1}
-]);
-
-/**
- * Verify that non-CRUD commands get logged when blocked by a refresh.
- */
-runTest(() => assert.commandWorked(mongos1DB.runCommand(
- {createIndexes: collName, indexes: [{key: {a: 1}, name: 'index'}]})),
- [
- {opType: 'countAllOperations', increase: 1},
- {opType: 'countCommands', increase: 1},
- ]);
-
-st.stop();
-})();
diff --git a/jstests/sharding/change_stream_no_drop.js b/jstests/sharding/change_stream_no_drop.js
new file mode 100644
index 00000000000..d378421cd18
--- /dev/null
+++ b/jstests/sharding/change_stream_no_drop.js
@@ -0,0 +1,62 @@
+/**
+ * DDL coordinator are responsible for dropping temporary collections, especially after failures.
+ * However, the change stream should not be aware of those events.
+ * @tags: [
+ * # Requires all nodes to be running the latest binary.
+ * multiversion_incompatible,
+ * ]
+ */
+function assertNoDrop(changeStream) {
+ while (changeStream.hasNext()) {
+ assert.neq(changeStream.next().operationType, 'drop');
+ }
+}
+
+function emptyChangeStream(changeStream) {
+ while (changeStream.hasNext()) {
+ changeStream.next();
+ }
+}
+
+(function() {
+
+const dbName = 'db';
+
+load('jstests/libs/fail_point_util.js'); // For configureFailPoint
+
+// Enable explicitly the periodic no-op writer to allow the router to process change stream events
+// coming from all shards. This is enabled for production clusters by default.
+const st = new ShardingTest({
+ mongos: 1,
+ config: 1,
+ shards: 2,
+ rs: {nodes: 1, setParameter: {writePeriodicNoops: true, periodicNoopIntervalSecs: 1}},
+ other: {enableBalancer: true}
+});
+
+// create a database and a change stream on it
+jsTest.log('Creating a change stream on ' + dbName);
+assert.commandWorked(
+ st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
+let changeStream = st.s.getDB('db').watch();
+
+// setFeatureCompatibilityVersion might cause dropping of deprecated collections
+emptyChangeStream(changeStream);
+
+jsTest.log(
+ 'The shard_collection_coordinator at second attempt (after failure) should not report drop events for orphaned');
+{
+ configureFailPoint(st.shard0,
+ 'failAtCommitCreateCollectionCoordinator',
+ data = {},
+ failPointMode = {times: 1});
+
+ collectionName = dbName + '.coll';
+ assert.commandWorked(st.s.adminCommand(
+ {shardCollection: collectionName, key: {_id: "hashed"}, numInitialChunks: 10}));
+
+ assertNoDrop(changeStream);
+}
+
+st.stop();
+}());
diff --git a/jstests/sharding/clone_catalog_data.js b/jstests/sharding/clone_catalog_data.js
index 62596057477..bd0651b0e78 100644
--- a/jstests/sharding/clone_catalog_data.js
+++ b/jstests/sharding/clone_catalog_data.js
@@ -106,14 +106,18 @@
checkOptions(c2, Object.keys(coll2Options));
checkUUID(c2, coll2uuid);
- function checkIndexes(collName, expectedIndexes) {
+ function checkIndexes(collName, expectedIndexes, shardedColl) {
var res = toShard.getDB('test').runCommand({listIndexes: collName});
assert.commandWorked(res, 'Failed to get indexes for collection ' + collName);
var indexes = res.cursor.firstBatch;
indexes.sort(sortByName);
- // There should be 3 indexes on each collection - the _id one, and the 2 we created.
- assert.eq(indexes.length, 3);
+ // TODO SERVER-74252: once 7.0 becomes LastLTS we can assume that the movePrimary will never
+ // copy indexes of sharded collections.
+ if (shardedColl)
+ assert(indexes.length === 1 || indexes.length === 3);
+ else
+ assert(indexes.length === 3);
indexes.forEach((index, i) => {
var expected;
@@ -127,8 +131,8 @@
});
}
- checkIndexes('coll1', coll1Indexes);
- checkIndexes('coll2', coll2Indexes);
+ checkIndexes('coll1', coll1Indexes, /*shardedColl*/ false);
+ checkIndexes('coll2', coll2Indexes, /*shardedColl*/ true);
// Verify that the data from the unsharded collections resides on the new primary shard, and was
// copied as part of the clone.
diff --git a/jstests/sharding/cluster_time_across_add_shard.js b/jstests/sharding/cluster_time_across_add_shard.js
new file mode 100644
index 00000000000..57c611cf1d2
--- /dev/null
+++ b/jstests/sharding/cluster_time_across_add_shard.js
@@ -0,0 +1,166 @@
+/**
+ * Test that a shardsvr replica set that has not initialized its shard identity via an
+ * addShard command can validate and sign cluster times, and that after its shard identity has
+ * been initialized, it is still able to validate cluster times that were signed when it was not a
+ * shard.
+ */
+
+(function() {
+"use strict";
+
+load("jstests/libs/fail_point_util.js");
+load("jstests/multiVersion/libs/multi_rs.js");
+load('jstests/replsets/rslib.js');
+
+function createUser(rst) {
+ rst.getPrimary().getDB("admin").createUser({user: "root", pwd: "root", roles: ["root"]},
+ {w: rst.nodes.length});
+}
+
+function authUser(node) {
+ assert(node.getDB("admin").auth("root", "root"));
+}
+
+function createSession(node) {
+ const conn = new Mongo(node.host);
+ authUser(conn);
+ return conn.startSession({causalConsistency: false, retryWrites: false});
+}
+
+function withTemporaryTestData(callback, mods = {}) {
+ const originalTestData = TestData;
+ try {
+ TestData = Object.assign({}, TestData, mods);
+ callback();
+ } finally {
+ TestData = originalTestData;
+ }
+}
+
+// Start a replica set with keyfile authentication enabled so it will return signed cluster times
+// its responses.
+const numNodes = 3;
+const keyFile = "jstests/libs/key1";
+const rstOpts = {
+ nodes: numNodes,
+ keyFile
+};
+const rst = new ReplSetTest(rstOpts);
+
+rst.startSet();
+rst.initiate();
+const primary = rst.getPrimary();
+
+// Create a user for running commands later on in the test. Make the user not have the
+// advanceClusterTime privilege. This ensures that the server will not return cluster times
+// signed with a dummy key.
+createUser(rst);
+
+let sessions = [];
+let sessionOnPrimary;
+rst.nodes.forEach(node => {
+ const session = createSession(node);
+ if (node == primary) {
+ sessionOnPrimary = session;
+ }
+ sessions.push(session);
+});
+
+const dbName = "testDb";
+const collName = "testColl";
+assert.commandWorked(sessionOnPrimary.getDatabase(dbName).getCollection(collName).insert({}));
+const lastClusterTime = sessionOnPrimary.getClusterTime();
+
+for (let session of sessions) {
+ session.advanceClusterTime(lastClusterTime);
+ assert.commandWorked(session.getDatabase("admin").runCommand("hello"));
+}
+
+// Restart the replica set as a shardsvr. Use TestData with
+// authentication settings so Mongo.prototype.getDB() takes care of re-authenticating after the
+// network connection is re-established during ReplSetTest.prototype.upgradeSet().
+const tmpTestData = {
+ auth: true,
+ keyFile,
+ authUser: "__system",
+ keyFileData: "foopdedoop",
+ authenticationDatabase: "local"
+};
+const upgradeOpts = {
+ appendOptions: true
+};
+// Restart the replica set as a shardsvr.
+withTemporaryTestData(() => {
+ rst.upgradeSet(Object.assign({"shardsvr": ""}, upgradeOpts));
+}, tmpTestData);
+
+for (let session of sessions) {
+ // Reconnect and re-authenticate after the network connection was closed due to restart.
+ const error = assert.throws(() => session.getDatabase("admin").runCommand("hello"));
+ assert(isNetworkError(error), error);
+ authUser(session.getClient());
+
+ // Verify that the application is able to use a signed cluster time although the addShard
+ // or transitionFromDedicatedConfigServer command has not been run.
+ assert.commandWorked(session.getDatabase("admin").runCommand("hello"));
+ assert.eq(session.getClusterTime().signature.keyId, lastClusterTime.signature.keyId);
+}
+
+// Start a sharded cluster and add the shardsvr replica set to it.
+const st = new ShardingTest({
+ mongos: 1,
+ config: 1,
+ shards: 1,
+ other: {keyFile},
+ configOptions: {
+ // Additionally test TTL deletion of key documents. To speed up the test, make the
+ // documents expire right away. To prevent the documents from being deleted before all
+ // cluster time validation testing is completed, make the TTL monitor have a large
+ // sleep interval at first and then lower it at the end of the test when verifying that
+ // the documents do get deleted by the TTL monitor.
+ setParameter: {newShardExistingClusterTimeKeysExpirationSecs: 1, ttlMonitorSleepSecs: 3600}
+ }
+});
+assert.commandWorked(st.s.adminCommand({addShard: rst.getURL()}));
+createUser(st.configRS);
+
+rst.awaitReplication();
+
+for (let session of sessions) {
+ // As a performance optimization, LogicalTimeValidator::validate() skips validating $clusterTime
+ // values which have a $clusterTime.clusterTime value smaller than the currently known signed
+ // $clusterTime value. It is possible (but not strictly guaranteed) for internal communication
+ // to have already happened between cluster members such that they all know about a signed
+ // $clusterTime value. This signed $clusterTime value would come from the new signing key
+ // generated by the config server primary. Here we use the alwaysValidateClientsClusterTime
+ // fail point to simulate the behavior of case when the internal communication with a signed
+ // $clusterTime value has not happened yet.
+ const fp = (() => {
+ const fpConn = new Mongo(session.getClient().host);
+ authUser(fpConn);
+ return configureFailPoint(fpConn, "alwaysValidateClientsClusterTime");
+ })();
+
+ // Verify that after the addShard or transitionFromDedicatedConfigServer command has been run,
+ // the application is still able to use the cluster time signed when the replica set was not a
+ // shard.
+ assert.commandWorked(session.getDatabase("admin").runCommand("hello"));
+
+ // Verify that the new cluster time was signed with the sharded cluster's key (generated by
+ // the config server) instead of the shardsvr replica set's key.
+ assert.neq(session.getClusterTime().signature.keyId, lastClusterTime.signature.keyId);
+
+ // Verify that the old cluster time can also be used against the mongos, config server, and
+ // other shard.
+ assert.commandWorked(st.s.getDB("admin").runCommand({hello: 1, $clusterTime: lastClusterTime}));
+ assert.commandWorked(st.configRS.getPrimary().getDB("admin").runCommand(
+ {hello: 1, $clusterTime: lastClusterTime}));
+ assert.commandWorked(
+ st.rs0.getPrimary().getDB("admin").runCommand({hello: 1, $clusterTime: lastClusterTime}));
+
+ fp.off();
+}
+
+st.stop();
+rst.stopSet();
+})();
diff --git a/jstests/sharding/collection_uuid_shard_capped_collection.js b/jstests/sharding/collection_uuid_shard_capped_collection.js
new file mode 100644
index 00000000000..3b26f26715b
--- /dev/null
+++ b/jstests/sharding/collection_uuid_shard_capped_collection.js
@@ -0,0 +1,45 @@
+/**
+ * Tests the collectionUUID parameter of the shardCollection command against capped collections.
+ *
+ * @tags: [
+ * requires_fcv_60,
+ * ]
+ */
+(function() {
+'use strict';
+
+const st = new ShardingTest({shards: 1});
+const mongos = st.s0;
+
+const db = mongos.getDB(jsTestName());
+const coll = db['cappedColl'];
+const collName = coll.getName();
+
+// Create a capped collection.
+assert.commandWorked(db.createCollection(collName, {capped: true, size: 1024}));
+assert.commandWorked(mongos.adminCommand({enableSharding: db.getName()}));
+
+// Ensure that we fail the shardCollection command with 'CollectionUUIDMismatch' when the UUID does
+// not correspond to the collection.
+const nonexistentUUID = UUID();
+let res = assert.commandFailedWithCode(
+ mongos.adminCommand(
+ {shardCollection: coll.getFullName(), key: {_id: 1}, collectionUUID: nonexistentUUID}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.db, db.getName());
+assert.eq(res.collectionUUID, nonexistentUUID);
+assert.eq(res.expectedCollection, collName);
+assert.eq(res.actualCollection, null);
+
+const uuid = assert.commandWorked(db.runCommand({listCollections: 1}))
+ .cursor.firstBatch.find(c => c.name === collName)
+ .info.uuid;
+
+// Ensure that we fail the shard command with 'InvalidOptions' when the UUID corresponds to the
+// capped collection.
+assert.commandFailedWithCode(
+ mongos.adminCommand({shardCollection: coll.getFullName(), collectionUUID: uuid, key: {_id: 1}}),
+ ErrorCodes.InvalidOptions);
+
+st.stop();
+})();
diff --git a/jstests/sharding/compound_hashed_shard_key_targeting.js b/jstests/sharding/compound_hashed_shard_key_targeting.js
index 9e4858ee462..59de60b9508 100644
--- a/jstests/sharding/compound_hashed_shard_key_targeting.js
+++ b/jstests/sharding/compound_hashed_shard_key_targeting.js
@@ -280,10 +280,5 @@ profileFilter = {
};
verifyProfilerEntryOnCorrectShard(1, profileFilter);
-// Test to verify that delete with limit:1, without full shard key in query fails.
-assert.commandFailedWithCode(
- coll.runCommand({delete: coll.getName(), deletes: [{q: {a: 1}, limit: 1}], ordered: false}),
- ErrorCodes.ShardKeyNotFound);
-
st.stop();
})();
diff --git a/jstests/sharding/data_size_aware_balancing_sessions_collection.js b/jstests/sharding/data_size_aware_balancing_sessions_collection.js
new file mode 100644
index 00000000000..a9d4acb4b68
--- /dev/null
+++ b/jstests/sharding/data_size_aware_balancing_sessions_collection.js
@@ -0,0 +1,197 @@
+/*
+ * Tests that the balancer splits the sessions collection and uniformly distributes the chunks
+ * across shards in the cluster.
+ * @tags: [
+ * featureFlagBalanceAccordingToDataSize,
+ * requires_fcv_60,
+ * resource_intensive,
+ * ]
+ */
+(function() {
+"use strict";
+
+load("jstests/libs/feature_flag_util.js");
+load("jstests/sharding/libs/find_chunks_util.js");
+
+// TODO SERVER-50144 Remove this and allow orphan checking.
+// This test calls removeShard which can leave docs in config.rangeDeletions in state "pending",
+// therefore preventing orphans from being cleaned up.
+TestData.skipCheckOrphans = true;
+
+/*
+ * Returns the number of chunks for the sessions collection.
+ */
+function getNumTotalChunks() {
+ return findChunksUtil.countChunksForNs(configDB, kSessionsNs);
+}
+
+/*
+ * Returns the number of chunks for the sessions collection that are the given shard.
+ */
+function getNumChunksOnShard(shardName) {
+ return findChunksUtil.countChunksForNs(configDB, kSessionsNs, {shard: shardName});
+}
+
+/*
+ * Returns the number of docs in the sessions collection on the given host.
+ */
+function getNumSessionDocs(conn) {
+ return conn.getCollection(kSessionsNs).find().itcount();
+}
+
+/*
+ * Starts a replica-set shard, adds the shard to the cluster, and increments numShards.
+ * Returns the ReplSetTest object for the shard.
+ */
+function addShardsToCluster(shardsToAdd) {
+ let addedReplicaSets = [];
+ for (let i = 0; i < shardsToAdd; ++i) {
+ const shardName = clusterName + "-rs" + numShards;
+ const replTest = new ReplSetTest({name: shardName, nodes: 1});
+ replTest.startSet({shardsvr: ""});
+ replTest.initiate();
+
+ assert.commandWorked(st.s.adminCommand({addShard: replTest.getURL(), name: shardName}));
+ numShards++;
+ addedReplicaSets.push(replTest);
+ }
+ return addedReplicaSets;
+}
+
+/*
+ * Removes the given shard from the cluster, waits util the state is completed, and
+ * decrements numShards.
+ */
+function removeShardFromCluster(shardName) {
+ assert.commandWorked(st.s.adminCommand({removeShard: shardName}));
+ assert.soon(function() {
+ const res = st.s.adminCommand({removeShard: shardName});
+ if (!res.ok && res.code === ErrorCodes.ShardNotFound) {
+ // If the config server primary steps down right after removing the config.shards doc
+ // for the shard but before responding with "state": "completed", the mongos would retry
+ // the _configsvrRemoveShard command against the new config server primary, which would
+ // not find the removed shard in its ShardRegistry if it has done a ShardRegistry reload
+ // after the config.shards doc for the shard was removed. This would cause the command
+ // to fail with ShardNotFound.
+ return true;
+ }
+ assert.commandWorked(res);
+ return ("completed" == res.state);
+ }, "failed to remove shard " + shardName, kBalancerTimeoutMS);
+ numShards--;
+}
+
+/*
+ * Returns the estimated size (in bytes) of the sessions collection chunks hosted by the shard.
+ */
+function getSessionsCollSizeInShard(shardStats) {
+ const orphansSize =
+ shardStats['storageStats']['numOrphanDocs'] * shardStats['storageStats']['avgObjSize'];
+ return shardStats['storageStats']['size'] - orphansSize;
+}
+
+function printSessionsCollectionDistribution(shards) {
+ const numDocsOnShards = shards.map(shard => getNumSessionDocs(shard));
+ const collStatsPipeline = [
+ {'$collStats': {'storageStats': {}}},
+ {
+ '$project': {
+ 'shard': true,
+ 'storageStats':
+ {'count': true, 'size': true, 'avgObjSize': true, 'numOrphanDocs': true}
+ }
+ },
+ {'$sort': {'shard': 1}}
+ ];
+ const collectionStorageStats =
+ st.s.getCollection(kSessionsNs).aggregate(collStatsPipeline).toArray();
+ const collSizeDistribution =
+ collectionStorageStats.map(shardStats => getSessionsCollSizeInShard(shardStats));
+ const numChunksOnShard = shards.map(shard => getNumChunksOnShard(shard.shardName));
+ const kMaxChunkSizeBytes = st.config.collections.findOne({_id: kSessionsNs}).maxChunkSizeBytes;
+
+ jsTest.log(`Sessions distribution across shards ${tojson(shards)}: #docs = ${
+ tojson(numDocsOnShards)}, #chunks = ${tojson(numChunksOnShard)}, size = ${
+ tojson(collSizeDistribution)}, #maxChunkSize: ${tojson(kMaxChunkSizeBytes)}`);
+}
+
+function waitUntilBalancedAndVerify(shards) {
+ const coll = st.s.getCollection(kSessionsNs);
+ st.awaitBalance(
+ kSessionsCollName, kConfigDbName, 9 * 60000 /* 9min timeout */, 1000 /* 1s interval */);
+ printSessionsCollectionDistribution(shards);
+ st.verifyCollectionIsBalanced(coll);
+}
+
+const kMinNumChunks = 100;
+const kExpectedNumChunks = 128; // the balancer rounds kMinNumChunks to the next power of 2.
+const kNumSessions = 2000;
+const kBalancerTimeoutMS = 5 * 60 * 1000;
+
+let numShards = 2;
+const clusterName = jsTest.name();
+const st = new ShardingTest({
+ name: clusterName,
+ shards: numShards,
+ other: {configOptions: {setParameter: {minNumChunksForSessionsCollection: kMinNumChunks}}}
+});
+
+const kConfigDbName = "config";
+const kSessionsCollName = "system.sessions";
+const kSessionsNs = `${kConfigDbName}.${kSessionsCollName}`;
+const configDB = st.s.getDB(kConfigDbName);
+
+// There is only one chunk initially.
+assert.eq(1, getNumTotalChunks());
+
+st.startBalancer();
+
+jsTest.log(
+ `Verify that the balancer generates the expected initial set of chunks for ${kSessionsNs}`);
+
+assert.soon(() => getNumTotalChunks() == kExpectedNumChunks,
+ "balancer did not split the initial chunk for the sessions collection");
+
+jsTest.log(`Verify that no chunks are moved from the primary shard of ${
+ kSessionsNs} if the are no open sessions`);
+{
+ st.awaitBalance(kSessionsCollName, kConfigDbName);
+ const numChunksInShard0 = getNumChunksOnShard(st.shard0.shardName);
+ const numChunksInShard1 = getNumChunksOnShard(st.shard1.shardName);
+ assert(numChunksInShard0 === kExpectedNumChunks && numChunksInShard1 === 0 ||
+ numChunksInShard1 === kExpectedNumChunks && numChunksInShard0 === 0);
+}
+
+jsTest.log(`Creating ${kNumSessions} sessions`);
+for (let i = 0; i < kNumSessions; i++) {
+ assert.commandWorked(st.s.adminCommand({startSession: 1}));
+}
+assert.commandWorked(st.s.adminCommand({refreshLogicalSessionCacheNow: 1}));
+assert.lte(kNumSessions, getNumSessionDocs(st.s));
+let shards = [st.shard0, st.shard1];
+jsTest.log(`Verify that the chunks of ${kSessionsNs} get distributed across the original cluster`);
+waitUntilBalancedAndVerify(shards);
+
+jsTest.log(
+ "Verify that the balancer redistributes chunks when more shards are added to the cluster");
+const addedReplicaSets = addShardsToCluster(3);
+shards = shards.concat(addedReplicaSets.map(rs => {
+ const primaryNode = rs.getPrimary();
+ primaryNode.shardName = rs.name;
+ return primaryNode;
+}));
+waitUntilBalancedAndVerify(shards);
+
+jsTest.log(
+ "Verify that the balancer redistributes chunks when shards are removed from the cluster");
+removeShardFromCluster(shards[2].shardName);
+shards.splice(2, 1);
+waitUntilBalancedAndVerify(shards);
+
+st.stopBalancer();
+
+st.stop();
+addedReplicaSets.forEach(rs => {
+ rs.stopSet();
+});
+}());
diff --git a/jstests/sharding/database_versioning_all_commands.js b/jstests/sharding/database_versioning_all_commands.js
index fe7c409f524..7218570110b 100644
--- a/jstests/sharding/database_versioning_all_commands.js
+++ b/jstests/sharding/database_versioning_all_commands.js
@@ -247,6 +247,8 @@ let testCases = {
_isSelf: {skip: "executes locally on mongos (not sent to any remote node)"},
_killOperations: {skip: "executes locally on mongos (not sent to any remote node)"},
_mergeAuthzCollections: {skip: "always targets the config server"},
+ _mongotConnPoolStats: {skip: "not on a user database", conditional: true},
+ _dropConnectionsToMongot: {skip: "not on a user database", conditional: true},
abortReshardCollection: {skip: "always targets the config server"},
abortTransaction: {skip: "unversioned and uses special targetting rules"},
addShard: {skip: "not on a user database"},
@@ -347,6 +349,7 @@ let testCases = {
}
},
createRole: {skip: "always targets the config server"},
+ createSearchIndexes: {skip: "executes locally on mongos"},
createUser: {skip: "always targets the config server"},
currentOp: {skip: "not on a user database"},
dataSize: {
@@ -410,6 +413,7 @@ let testCases = {
}
},
dropRole: {skip: "always targets the config server"},
+ dropSearchIndex: {skip: "executes locally on mongos"},
dropUser: {skip: "always targets the config server"},
echo: {skip: "does not forward command to primary shard"},
enableSharding: {skip: "does not forward command to primary shard"},
@@ -511,6 +515,7 @@ let testCases = {
},
}
},
+ listSearchIndexes: {skip: "executes locally on mongos"},
listShards: {skip: "does not forward command to primary shard"},
logApplicationMessage: {skip: "not on a user database", conditional: true},
logMessage: {skip: "not on a user database"},
@@ -649,8 +654,7 @@ let testCases = {
}
},
setFeatureCompatibilityVersion: {skip: "not on a user database"},
- setFreeMonitoring:
- {skip: "explicitly fails for mongos, primary mongod only", conditional: true},
+ setProfilingFilterGlobally: {skip: "executes locally on mongos (not sent to any remote node)"},
setParameter: {skip: "executes locally on mongos (not sent to any remote node)"},
setClusterParameter: {skip: "always targets the config server"},
setUserWriteBlockMode: {skip: "executes locally on mongos (not sent to any remote node)"},
@@ -690,6 +694,7 @@ let testCases = {
}
},
updateRole: {skip: "always targets the config server"},
+ updateSearchIndex: {skip: "executes locally on mongos"},
updateUser: {skip: "always targets the config server"},
updateZoneKeyRange: {skip: "not on a user database"},
usersInfo: {skip: "always targets the config server"},
diff --git a/jstests/sharding/documents_db_not_exist.js b/jstests/sharding/documents_db_not_exist.js
new file mode 100644
index 00000000000..1742d149516
--- /dev/null
+++ b/jstests/sharding/documents_db_not_exist.js
@@ -0,0 +1,48 @@
+/**
+ * Tests that $documents stage continues even when the database does not exist
+ * @tags: [requires_fcv_62, multiversion_incompatible]
+ *
+ */
+
+(function() {
+"use strict";
+
+let st = new ShardingTest({shards: 3});
+
+function listDatabases(options) {
+ return assert.commandWorked(st.s.adminCommand(Object.assign({listDatabases: 1}, options)))
+ .databases;
+}
+
+function createAndDropDatabase(dbName) {
+ // Create the database.
+ let db = st.s.getDB(dbName);
+ assert.commandWorked(db.foo.insert({}));
+ // Confirms the database exists.
+ assert.eq(1, listDatabases({nameOnly: true, filter: {name: dbName}}).length);
+ // Drop the database
+ assert.commandWorked(db.dropDatabase());
+ // Confirm the database is dropped.
+ assert.eq(0, listDatabases({nameOnly: true, filter: {name: dbName}}).length);
+ return db;
+}
+
+// $documents stage evaluates to an array of objects.
+let db = createAndDropDatabase("test");
+let documents = [];
+for (let i = 0; i < 50; i++) {
+ documents.push({_id: i});
+}
+let result = db.aggregate([{$documents: documents}]);
+assert(result.toArray().length == 50);
+
+//$documents stage evaluates to an array of objects in a pipeline
+db = createAndDropDatabase("test2");
+result = db.aggregate([
+ {$documents: [{_id: 1, size: "medium"}, {_id: 2, size: "large"}]},
+ {$match: {size: "medium"}}
+]);
+assert(result.toArray().length == 1);
+
+st.stop();
+})();
diff --git a/jstests/sharding/documents_sharded.js b/jstests/sharding/documents_sharded.js
new file mode 100644
index 00000000000..85b20cd29c3
--- /dev/null
+++ b/jstests/sharding/documents_sharded.js
@@ -0,0 +1,160 @@
+/**
+ * This is the test for $documents stage in aggregation pipeline on a sharded collection.
+ * @tags: [ do_not_wrap_aggregations_in_facets, requires_fcv_51 ]
+ *
+ */
+
+(function() {
+"use strict";
+
+load("jstests/aggregation/extras/utils.js"); // For resultsEq.
+
+let st = new ShardingTest({shards: 2});
+const db = st.s.getDB(jsTestName());
+const dbName = db.getName();
+assert.commandWorked(db.adminCommand({enableSharding: dbName}));
+
+// Create sharded collections.
+const coll = db['shardedColl'];
+st.shardColl(coll, {x: 1}, {x: 1}, {x: 1}, dbName);
+
+const lookup_coll = db['lookupColl'];
+st.shardColl(lookup_coll, {id_name: 1}, {id_name: 1}, {id_name: 1}, dbName);
+for (let i = 0; i < 10; i++) {
+ assert.commandWorked(lookup_coll.insert({id_name: i, name: "name_" + i}));
+}
+
+// $documents given an array of objects.
+const docs = db.aggregate([{$documents: [{a1: 1}, {a1: 2}]}]).toArray();
+
+assert.eq(2, docs.length);
+assert.eq(docs[0], {a1: 1});
+assert.eq(docs[1], {a1: 2});
+
+// $documents evaluates to an array of objects.
+const docs1 =
+ db.aggregate([{$documents: {$map: {input: {$range: [0, 100]}, in : {x: "$$this"}}}}]).toArray();
+
+assert.eq(100, docs1.length);
+for (let i = 0; i < 100; i++) {
+ assert.eq(docs1[i], {x: i});
+}
+
+// $documents evaluates to an array of objects.
+const docsUnionWith =
+ coll.aggregate([
+ {
+ $unionWith: {
+ pipeline: [{$documents: {$map: {input: {$range: [0, 5]}, in : {x: "$$this"}}}}]
+ }
+ },
+ {$group: {_id: "$x", x: {$first: "$x"}}},
+ {$project: {_id: 0}},
+ ])
+ .toArray();
+assert(resultsEq([{x: 0}, {x: 1}, {x: 2}, {x: 3}, {x: 4}], docsUnionWith));
+
+{ // $documents with const objects inside $unionWith.
+ const res = coll.aggregate([
+ {$unionWith: {pipeline: [{$documents: [{x: 1}, {x: 2}]}]}},
+ {$group: {_id: "$x", x: {$first: "$x"}}},
+ {$project: {_id: 0}}
+ ])
+ .toArray();
+ assert(resultsEq([{x: 1}, {x: 2}], res));
+}
+
+{ // $documents with const objects inside $lookup (no "coll", explicit $match).
+ const res = lookup_coll.aggregate([
+ {
+ $lookup: {
+ let: {"id_lookup": "$id_name"},
+ pipeline: [
+ {$documents: [{xx: 1}, {xx: 2}, {xx : 3}]},
+ {
+ $match:
+ {
+ $expr:
+ {
+ $eq:
+ ["$$id_lookup", "$xx"]
+ }
+ }
+ }
+ ],
+ as: "names"
+ }
+ },
+ {$match: {"names": {"$ne": []}}},
+ {$project: {_id: 0}}
+ ]
+ )
+ .toArray();
+ assert(resultsEq(
+ [
+ {id_name: 1, name: "name_1", names: [{"xx": 1}]},
+ {id_name: 2, name: "name_2", names: [{"xx": 2}]},
+ {id_name: 3, name: "name_3", names: [{"xx": 3}]}
+ ],
+ res));
+}
+{ // $documents with const objects inside $lookup (no "coll", + localField/foreignField).
+ const res = lookup_coll.aggregate([
+ {
+ $lookup: {
+ localField: "id_name",
+ foreignField: "xx",
+ pipeline: [
+ {$documents: [{xx: 1}, {xx: 2}, {xx: 3}]}
+ ],
+ as: "names"
+ }
+ },
+ {$match: {"names": {"$ne": []}}},
+ {$project: {_id: 0}}
+ ])
+ .toArray();
+ assert(resultsEq(
+ [
+ {id_name: 1, name: "name_1", names: [{"xx": 1}]},
+ {id_name: 2, name: "name_2", names: [{"xx": 2}]},
+ {id_name: 3, name: "name_3", names: [{"xx": 3}]}
+ ],
+ res));
+}
+
+// Must fail when $document appears in the top level collection pipeline.
+assert.throwsWithCode(() => {
+ coll.aggregate([{$documents: {$map: {input: {$range: [0, 100]}, in : {x: "$$this"}}}}]);
+}, ErrorCodes.InvalidNamespace);
+
+// Must fail due to misplaced $document.
+assert.throwsWithCode(() => {
+ coll.aggregate([{$project: {x: [{xx: 1}, {xx: 2}]}}, {$documents: [{x: 1}]}]);
+}, 40602);
+
+// Test that $documents fails due to producing array of non-objects.
+assert.throwsWithCode(() => {
+ db.aggregate([{$documents: [1, 2, 3]}]);
+}, 40228);
+
+// Now with one object and one scalar.
+assert.throwsWithCode(() => {
+ db.aggregate([{$documents: [{x: 1}, 2]}]);
+}, 40228);
+
+// Test that $documents fails due when provided a non-array.
+assert.throwsWithCode(() => {
+ db.aggregate([{$documents: "string"}]);
+}, 5858203);
+
+// Test that $documents succeeds when given a singleton object.
+assert.eq(db.aggregate([{$documents: [{x: [1, 2, 3]}]}]).toArray(), [{x: [1, 2, 3]}]);
+
+// Must fail when $document appears in the top level collection pipeline.
+assert.throwsWithCode(() => {
+ coll.aggregate([{$documents: {$map: {input: {$range: [0, 100]}, in : {x: "$$this"}}}}]);
+}, ErrorCodes.InvalidNamespace);
+
+st.stop();
+})(); \ No newline at end of file
diff --git a/jstests/sharding/drop_collection.js b/jstests/sharding/drop_collection.js
index f07ced08ae5..0947b56124a 100644
--- a/jstests/sharding/drop_collection.js
+++ b/jstests/sharding/drop_collection.js
@@ -32,17 +32,29 @@ function assertCollectionDropped(ns, uuid = null) {
configDB.tags.countDocuments({ns: ns}),
"Found unexpected tag for a collection after drop.");
+ // Sharding metadata checks
+
+ // No more coll entry
+ assert.eq(null, st.s.getCollection(ns).exists());
+ assert.eq(0,
+ configDB.collections.countDocuments({_id: ns}),
+ "Found collection entry in 'config.collection' after drop.");
+
// No more chunks
- const errMsg = "Found collection entry in 'config.collection' after drop.";
- // Before 5.0 chunks were indexed by ns, now by uuid
- assert.eq(0, configDB.chunks.countDocuments({ns: ns}), errMsg);
if (uuid != null) {
- assert.eq(0, configDB.chunks.countDocuments({uuid: uuid}), errMsg);
+ assert.eq(0,
+ configDB.chunks.countDocuments({uuid: uuid}),
+ "Found references to collection uuid in 'config.chunks' after drop.");
}
- // No more coll entry
- assert.eq(null, st.s.getCollection(ns).exists());
- assert.eq(0, configDB.collections.countDocuments({_id: ns}));
+ // Verify that persisted cached metadata was removed as part of the dropCollection
+ const chunksCollName = 'cache.chunks.' + ns;
+ for (let configDb of [st.shard0.getDB('config'), st.shard1.getDB('config')]) {
+ assert.eq(configDb['cache.collections'].countDocuments({_id: ns}),
+ 0,
+ "Found collection entry in 'config.cache.collections' after drop.");
+ assert(!configDb[chunksCollName].exists());
+ }
}
jsTest.log("Drop unsharded collection.");
@@ -74,10 +86,11 @@ jsTest.log("Drop unsharded collection also remove tags.");
assert.commandWorked(db.runCommand({drop: coll.getName()}));
assertCollectionDropped(coll.getFullName());
}
+
jsTest.log("Drop sharded collection repeated.");
{
const db = getNewDb();
- const coll = db['unshardedColl0'];
+ const coll = db['shardedColl0'];
// Create the database
assert.commandWorked(st.s.adminCommand({enableSharding: db.getName()}));
for (var i = 0; i < 3; i++) {
@@ -85,9 +98,11 @@ jsTest.log("Drop sharded collection repeated.");
assert.commandWorked(st.s.adminCommand({shardCollection: coll.getFullName(), key: {x: 1}}));
assert.commandWorked(coll.insert({x: 123}));
assert.eq(1, coll.countDocuments({x: 123}));
+
// Drop the collection
+ var uuid = getCollectionUUID(coll.getFullName());
assert.commandWorked(db.runCommand({drop: coll.getName()}));
- assertCollectionDropped(coll.getFullName());
+ assertCollectionDropped(coll.getFullName(), uuid);
}
}
@@ -245,7 +260,9 @@ jsTest.log("Move primary with drop and recreate - new primary own chunks.");
configDB, coll.getFullName(), {shard: st.shard1.shardName}));
jsTest.log("Drop sharded collection");
+ var uuid = getCollectionUUID(coll.getFullName());
coll.drop();
+ assertCollectionDropped(coll.getFullName(), uuid);
jsTest.log("Re-Create sharded collection with one chunk on shard 0");
st.shardColl(coll, {skey: 1}, false, false);
@@ -257,11 +274,12 @@ jsTest.log("Move primary with drop and recreate - new primary own chunks.");
st.ensurePrimaryShard(db.getName(), st.shard1.shardName);
jsTest.log("Drop sharded collection");
+ uuid = getCollectionUUID(coll.getFullName());
coll.drop();
+ assertCollectionDropped(coll.getFullName(), uuid);
}
-jsTest.log(
- "Test that dropping a non-sharded collection, relevant events are properly logged on CSRS");
+jsTest.log("Test that dropping an unsharded collection, relevant events are logged on CSRS.");
{
// Create a non-sharded collection
const db = getNewDb();
@@ -270,6 +288,7 @@ jsTest.log(
// Drop the collection
assert.commandWorked(db.runCommand({drop: coll.getName()}));
+ assertCollectionDropped(coll.getFullName());
// Verify that the drop collection start event has been logged
const startLogCount =
@@ -282,7 +301,7 @@ jsTest.log(
assert.gte(1, endLogCount, "dropCollection end event not found in changelog");
}
-jsTest.log("Test that dropping a sharded collection, relevant events are properly logged on CSRS");
+jsTest.log("Test that dropping a sharded collection, relevant events are logged on CSRS.");
{
// Create a sharded collection
const db = getNewDb();
@@ -299,7 +318,9 @@ jsTest.log("Test that dropping a sharded collection, relevant events are properl
assert.commandWorked(coll.insert({_id: -10}));
// Drop the collection
+ const uuid = getCollectionUUID(coll.getFullName());
assert.commandWorked(db.runCommand({drop: coll.getName()}));
+ assertCollectionDropped(coll.getFullName(), uuid);
// Verify that the drop collection start event has been logged
const startLogCount =
@@ -312,7 +333,7 @@ jsTest.log("Test that dropping a sharded collection, relevant events are properl
assert.gte(1, endLogCount, "dropCollection end event not found in changelog");
}
-jsTest.log("Test that dropping a sharded collection, the cached metadata on shards is cleaned up");
+jsTest.log("Test that dropping a sharded collection, the cached metadata on shards is cleaned up.");
{
// Create a sharded collection
const db = getNewDb();
@@ -320,24 +341,21 @@ jsTest.log("Test that dropping a sharded collection, the cached metadata on shar
assert.commandWorked(
st.s.adminCommand({enableSharding: db.getName(), primaryShard: st.shard0.shardName}));
assert.commandWorked(st.s.adminCommand({shardCollection: coll.getFullName(), key: {_id: 1}}));
-
- // Distribute the chunks among the shards
- assert.commandWorked(st.s.adminCommand({split: coll.getFullName(), middle: {_id: 0}}));
assert.commandWorked(coll.insert({_id: 10}));
- assert.commandWorked(coll.insert({_id: -10}));
- // Get the chunks cache collection name
- const configCollDoc = st.s0.getDB('config').collections.findOne({_id: coll.getFullName()});
- const chunksCollName = 'cache.chunks.' + coll.getFullName();
+ // At this point only one shard has valid filtering information (i.e. the one that has data).
+ // Below we are forcing to all shards to have their valid filtering information. For the shard
+ // that doesn't own any data, that means having a filtering information stating that no chunks
+ // are owned by that shard.
+ assert.commandWorked(
+ st.shard0.adminCommand({_flushRoutingTableCacheUpdates: coll.getFullName()}));
+ assert.commandWorked(
+ st.shard1.adminCommand({_flushRoutingTableCacheUpdates: coll.getFullName()}));
// Drop the collection
+ const uuid = getCollectionUUID(coll.getFullName());
assert.commandWorked(db.runCommand({drop: coll.getName()}));
-
- // Verify that the cached metadata on shards is cleaned up
- for (let configDb of [st.shard0.getDB('config'), st.shard1.getDB('config')]) {
- assert.eq(configDb['cache.collections'].countDocuments({_id: coll.getFullName()}), 0);
- assert(!configDb[chunksCollName].exists());
- }
+ assertCollectionDropped(coll.getFullName(), uuid);
}
st.stop();
diff --git a/jstests/sharding/drop_collection_if_uuid_not_matching.js b/jstests/sharding/drop_collection_if_uuid_not_matching.js
index 59dde47a364..81a0dacd2f7 100644
--- a/jstests/sharding/drop_collection_if_uuid_not_matching.js
+++ b/jstests/sharding/drop_collection_if_uuid_not_matching.js
@@ -5,8 +5,8 @@
* - Keep the collection if the uuid is exactly the expected one.
*
* @tags: [
- * requires_fcv_51, // The command is not present in v5.0
- * does_not_support_stepdowns, // The command is not resilient to stepdowns
+ * requires_fcv_51, # The command is not present in v5.0
+ * does_not_support_stepdowns, # The command is not resilient to stepdowns
* ]
*/
diff --git a/jstests/sharding/enforce_zone_policy.js b/jstests/sharding/enforce_zone_policy.js
index 11a43d2572d..2ded590cb09 100644
--- a/jstests/sharding/enforce_zone_policy.js
+++ b/jstests/sharding/enforce_zone_policy.js
@@ -5,25 +5,29 @@
load("jstests/sharding/libs/find_chunks_util.js");
-var st = new ShardingTest({shards: 3, mongos: 1});
+const st = new ShardingTest({shards: 3, mongos: 1, other: {chunkSize: 1, enableAutoSplit: false}});
+const dbName = 'test';
+const collName = 'foo';
+const ns = dbName + '.' + collName;
-assert.commandWorked(st.s0.adminCommand({enablesharding: 'test'}));
-st.ensurePrimaryShard('test', st.shard1.shardName);
+assert.commandWorked(
+ st.s0.adminCommand({enablesharding: dbName, primaryShard: st.shard1.shardName}));
-var testDB = st.s0.getDB('test');
+var testDB = st.s0.getDB(dbName);
var configDB = st.s0.getDB('config');
+assert.commandWorked(st.s0.adminCommand({shardCollection: ns, key: {_id: 1}}));
+
+const bigString = 'X'.repeat(1024 * 1024); // 1MB
var bulk = testDB.foo.initializeUnorderedBulkOp();
for (var i = 0; i < 9; i++) {
- bulk.insert({_id: i, x: i});
+ bulk.insert({_id: i, x: bigString});
}
assert.commandWorked(bulk.execute());
-assert.commandWorked(st.s0.adminCommand({shardCollection: 'test.foo', key: {_id: 1}}));
-
// Produce 9 chunks with min value at the documents just inserted
for (var i = 0; i < 8; i++) {
- assert.commandWorked(st.s0.adminCommand({split: 'test.foo', middle: {_id: i}}));
+ assert.commandWorked(st.s0.adminCommand({split: ns, middle: {_id: i}}));
}
/**
@@ -35,7 +39,7 @@ function assertBalanceCompleteAndStable(checkFunc, stepName) {
assert.soon(checkFunc, 'Balance at step ' + stepName + ' did not happen', 3 * 60 * 1000, 2000);
- st.waitForBalancer(true, 60000);
+ st.awaitBalancerRound();
st.printShardingStatus(true);
assert(checkFunc());
@@ -47,8 +51,8 @@ function assertBalanceCompleteAndStable(checkFunc, stepName) {
* cluster is evenly balanced.
*/
function checkClusterEvenlyBalanced() {
- var maxChunkDiff = st.chunkDiff('foo', 'test');
- return maxChunkDiff <= 1;
+ assert.commandWorked(st.s.getDB('admin').runCommand({balancerStatus: 1}));
+ return true;
}
st.startBalancer();
@@ -59,17 +63,16 @@ assertBalanceCompleteAndStable(checkClusterEvenlyBalanced, 'initial');
// Spread chunks correctly across zones
st.addShardTag(st.shard0.shardName, 'a');
st.addShardTag(st.shard1.shardName, 'a');
-st.addTagRange('test.foo', {_id: -100}, {_id: 100}, 'a');
+st.addTagRange(ns, {_id: -100}, {_id: 100}, 'a');
st.addShardTag(st.shard2.shardName, 'b');
-st.addTagRange('test.foo', {_id: MinKey}, {_id: -100}, 'b');
-st.addTagRange('test.foo', {_id: 100}, {_id: MaxKey}, 'b');
+st.addTagRange(ns, {_id: MinKey}, {_id: -100}, 'b');
+st.addTagRange(ns, {_id: 100}, {_id: MaxKey}, 'b');
assertBalanceCompleteAndStable(function() {
- var chunksOnShard2 =
- findChunksUtil.findChunksByNs(configDB, 'test.foo', {shard: st.shard2.shardName})
- .sort({min: 1})
- .toArray();
+ var chunksOnShard2 = findChunksUtil.findChunksByNs(configDB, ns, {shard: st.shard2.shardName})
+ .sort({min: 1})
+ .toArray();
jsTestLog('Chunks on shard2: ' + tojson(chunksOnShard2));
@@ -82,24 +85,25 @@ assertBalanceCompleteAndStable(function() {
}, 'chunks to zones a and b');
// Tag the entire collection to shard0 and wait for everything to move to that shard
-st.removeTagRange('test.foo', {_id: -100}, {_id: 100}, 'a');
-st.removeTagRange('test.foo', {_id: MinKey}, {_id: -100}, 'b');
-st.removeTagRange('test.foo', {_id: 100}, {_id: MaxKey}, 'b');
+st.removeTagRange(ns, {_id: -100}, {_id: 100}, 'a');
+st.removeTagRange(ns, {_id: MinKey}, {_id: -100}, 'b');
+st.removeTagRange(ns, {_id: 100}, {_id: MaxKey}, 'b');
st.removeShardTag(st.shard1.shardName, 'a');
st.removeShardTag(st.shard2.shardName, 'b');
-st.addTagRange('test.foo', {_id: MinKey}, {_id: MaxKey}, 'a');
+st.addTagRange(ns, {_id: MinKey}, {_id: MaxKey}, 'a');
assertBalanceCompleteAndStable(function() {
- var counts = st.chunkCounts('foo');
+ var counts = st.chunkCounts(collName);
printjson(counts);
- return counts[st.shard0.shardName] == 11 && counts[st.shard1.shardName] == 0 &&
+ // All chunks must have been moved to shard 0, none left on shard 1 and 2
+ return counts[st.shard0.shardName] > 0 && counts[st.shard1.shardName] == 0 &&
counts[st.shard2.shardName] == 0;
}, 'all chunks to zone a');
// Remove all zones and ensure collection is correctly redistributed
st.removeShardTag(st.shard0.shardName, 'a');
-st.removeTagRange('test.foo', {_id: MinKey}, {_id: MaxKey}, 'a');
+st.removeTagRange(ns, {_id: MinKey}, {_id: MaxKey}, 'a');
assertBalanceCompleteAndStable(checkClusterEvenlyBalanced, 'final');
diff --git a/jstests/sharding/error_propagation.js b/jstests/sharding/error_propagation.js
index b4bb0b72331..1a74270f745 100644
--- a/jstests/sharding/error_propagation.js
+++ b/jstests/sharding/error_propagation.js
@@ -20,6 +20,6 @@ assert.commandWorked(db.foo.insert({a: [1, 2]}, {writeConcern: {w: 3}}));
var res = db.runCommand(
{aggregate: 'foo', pipeline: [{$project: {total: {'$add': ['$a', 1]}}}], cursor: {}});
-assert.commandFailedWithCode(res, 16554);
+assert.commandFailedWithCode(res, [16554, ErrorCodes.TypeMismatch]);
st.stop();
}());
diff --git a/jstests/sharding/exhaust_hello_topology_changes.js b/jstests/sharding/exhaust_hello_topology_changes.js
index 51b381838a5..b0173c9bc40 100644
--- a/jstests/sharding/exhaust_hello_topology_changes.js
+++ b/jstests/sharding/exhaust_hello_topology_changes.js
@@ -1,8 +1,8 @@
/**
- * Test to check that the RSM receives an isMaster reply "immediately" (or "quickly") after a RS
+ * Test to check that the RSM receives a hello reply "immediately" (or "quickly") after a RS
* topology change when using the exhaust protocol. In order to test this, we'll set the
* maxAwaitTimeMS to much higher than the default (5 mins). This will allow us to assert that the
- * RSM receives the isMaster replies because of a topology change rather than maxAwaitTimeMS being
+ * RSM receives the hello replies because of a topology change rather than maxAwaitTimeMS being
* hit. A replica set node should send a response to the mongos as soon as it processes a topology
* change, so "immediately"/"quickly" can vary - we specify 5 seconds in this test ('timeoutMS').
*
diff --git a/jstests/sharding/extract_shard_key_values.js b/jstests/sharding/extract_shard_key_values.js
index 6969ad2b432..205cbc7f7b1 100644
--- a/jstests/sharding/extract_shard_key_values.js
+++ b/jstests/sharding/extract_shard_key_values.js
@@ -170,13 +170,5 @@ assert.writeErrorWithCode(
mongos.getCollection(kNsName).update({b: 1}, {$set: {c: 2}}, {upsert: true}),
ErrorCodes.ShardKeyNotFound);
-// Find and modify will not treat missing shard key values as null and require the full shard key to
-// be specified.
-assert.commandWorked(sessionColl.insert({_id: "findAndModify", a: 1}));
-assert.commandFailedWithCode(
- sessionDB.runCommand(
- {findAndModify: kCollName, query: {a: 1}, update: {$set: {updated: true}}}),
- ErrorCodes.ShardKeyNotFound);
-
st.stop();
})();
diff --git a/jstests/sharding/findandmodify_autosplit.js b/jstests/sharding/findandmodify_autosplit.js
index f73a1cfa029..9860df6ed83 100644
--- a/jstests/sharding/findandmodify_autosplit.js
+++ b/jstests/sharding/findandmodify_autosplit.js
@@ -5,9 +5,19 @@
'use strict';
load('jstests/sharding/autosplit_include.js');
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js"); // for FeatureFlagUtil.isEnabled
var st = new ShardingTest({shards: 1, mongos: 1, other: {chunkSize: 1, enableAutoSplit: true}});
+// TODO SERVER-66652 remove this test after 7.0 branches out
+const noMoreAutoSplitterFeatureFlag =
+ FeatureFlagUtil.isEnabled(st.configRS.getPrimary().getDB('admin'), "NoMoreAutoSplitter");
+if (noMoreAutoSplitterFeatureFlag) {
+ jsTestLog("Skipping as featureFlagNoMoreAutosplitter is enabled");
+ st.stop();
+ return;
+}
+
/* Return total number of chunks for a specific collection */
function getNumChunksForColl(coll) {
return findChunksUtil.countChunksForNs(st.getDB('config'), coll.getFullName());
diff --git a/jstests/sharding/hidden_index.js b/jstests/sharding/hidden_index.js
new file mode 100644
index 00000000000..5dfdce03dfb
--- /dev/null
+++ b/jstests/sharding/hidden_index.js
@@ -0,0 +1,86 @@
+/**
+ * Test to validate that a shard key index cannot be hidden if it cannot be dropped.
+ * @tags: [
+ * requires_fcv_60,
+ * ]
+ */
+
+(function() {
+'use strict';
+load("jstests/libs/index_catalog_helpers.js"); // For IndexCatalogHelpers.findByName.
+
+// Test to validate the correct behaviour of hiding an index in a sharded cluster with a shard key.
+function validateHiddenIndexBehaviour() {
+ let index_type = 1;
+ let index_name = "a_" + index_type;
+ assert.commandWorked(coll.createIndex({"a": index_type}));
+
+ let idxSpec = IndexCatalogHelpers.findByName(coll.getIndexes(), index_name);
+ assert.eq(idxSpec.hidden, undefined);
+
+ assert.commandWorked(coll.hideIndex(index_name));
+ idxSpec = IndexCatalogHelpers.findByName(coll.getIndexes(), index_name);
+ assert(idxSpec.hidden);
+
+ assert.commandWorked(coll.unhideIndex(index_name));
+ idxSpec = IndexCatalogHelpers.findByName(coll.getIndexes(), index_name);
+ assert.eq(idxSpec.hidden, undefined);
+
+ assert.commandWorked(coll.dropIndex(index_name));
+ assert.commandWorked(coll.createIndex({"a": index_type}, {hidden: true}));
+
+ idxSpec = IndexCatalogHelpers.findByName(coll.getIndexes(), index_name);
+ assert(idxSpec.hidden);
+ assert.commandWorked(coll.dropIndexes());
+}
+
+// Check that command will fail when we try to hide the only shard key index of the collection
+function validateOneShardKeyHiddenIndexBehaviour() {
+ assert.commandFailedWithCode(coll.hideIndex({skey: 1}), ErrorCodes.InvalidOptions);
+ assert.commandFailedWithCode(coll.hideIndex("skey_1"), ErrorCodes.InvalidOptions);
+ assert.commandFailedWithCode(
+ testDb.runCommand({"collMod": coll.getName(), "index": {"name": "skey_1", "hidden": true}}),
+ ErrorCodes.InvalidOptions);
+}
+
+// Check that command will fail when we try to hide or drop an index that is the last shard key
+// index of the collection
+function validateDifferentHiddenIndexesBehaviour() {
+ // Create index on skey
+ assert.commandWorked(coll.createIndex({skey: 1, anotherkey: 1}));
+
+ // Check that is possible to hide a shard key index using its key pattern
+ assert.commandWorked(coll.hideIndex({skey: 1}));
+ assert.commandWorked(coll.unhideIndex({skey: 1}));
+
+ // Check that is possible to hide a shard key index using its name
+ assert.commandWorked(testDb.runCommand(
+ {"collMod": coll.getName(), "index": {"name": "skey_1", "hidden": true}}));
+
+ assert.commandFailedWithCode(
+ testDb.runCommand(
+ {"collMod": coll.getName(), "index": {"name": "skey_1_anotherkey_1", "hidden": true}}),
+ ErrorCodes.InvalidOptions);
+
+ assert.commandFailed(coll.dropIndex("skey_1_anotherkey_1"));
+}
+
+// Configure initial sharded cluster
+const st = new ShardingTest({shards: 2});
+const mongos = st.s;
+const testDb = mongos.getDB("test");
+const coll = testDb.getCollection("foo");
+
+// Enable sharding at collection 'foo' and create a new shard key
+assert.commandWorked(st.s.adminCommand({enableSharding: testDb.getName()}));
+
+// Crate a new shard key
+assert.commandWorked(
+ st.s.adminCommand({shardcollection: testDb.getName() + '.' + coll.getName(), key: {skey: 1}}));
+
+validateHiddenIndexBehaviour();
+validateOneShardKeyHiddenIndexBehaviour();
+validateDifferentHiddenIndexesBehaviour();
+
+st.stop();
+})();
diff --git a/jstests/sharding/implicit_create_collection_triggered_by_DDLs.js b/jstests/sharding/implicit_create_collection_triggered_by_DDLs.js
new file mode 100644
index 00000000000..f500091c7cf
--- /dev/null
+++ b/jstests/sharding/implicit_create_collection_triggered_by_DDLs.js
@@ -0,0 +1,56 @@
+(function() {
+"use strict";
+
+function shardKnowledgeIsShardedOrUnknown(shard, nss) {
+ let res = assert.commandWorked(shard.adminCommand({getShardVersion: nss, fullMetadata: true}));
+ return (typeof res.global == 'string' && res.global == 'UNKNOWN') ||
+ (typeof res.metadata == 'object' && typeof res.metadata.collVersion != 'undefined');
+}
+
+const st = new ShardingTest({shards: 2, mongos: 1});
+
+void function testOptimizedShardCollection() {
+ const dbName = 'testDB1';
+ const collName = 'testColl1';
+
+ jsTest.log("Testing that implicit collection creation triggered by optimized " +
+ "shardCollection leaves all shards with the expected knowledge");
+
+ assert.commandWorked(
+ st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
+
+ assert.commandWorked(
+ st.s.adminCommand({shardCollection: `${dbName}.${collName}`, key: {_id: 'hashed'}}));
+
+ assert(shardKnowledgeIsShardedOrUnknown(st.shard0, `${dbName}.${collName}`),
+ "Unexpected sharding state in Shard 0");
+ assert(shardKnowledgeIsShardedOrUnknown(st.shard1, `${dbName}.${collName}`),
+ "Unexpected sharding state in Shard 1");
+}();
+
+void function testmovePrimary() {
+ const dbName = 'testDB2';
+ const collName = 'testColl2';
+
+ jsTest.log("Testing that implicit collection creation triggered by movePrimary " +
+ "leaves all shards with the expected knowledge");
+
+ assert.commandWorked(
+ st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
+
+ assert.commandWorked(
+ st.s.adminCommand({shardCollection: `${dbName}.${collName}`, key: {_id: 1}}));
+
+ assert.commandWorked(st.s.adminCommand({
+ movePrimary: dbName,
+ to: st.shard1.name,
+ }));
+
+ assert(shardKnowledgeIsShardedOrUnknown(st.shard0, `${dbName}.${collName}`),
+ "Unexpected sharding state in Shard 0");
+ assert(shardKnowledgeIsShardedOrUnknown(st.shard1, `${dbName}.${collName}`),
+ "Unexpected sharding state in Shard 1");
+}();
+
+st.stop();
+})(); \ No newline at end of file
diff --git a/jstests/sharding/index_operations_abort_concurrent_outgoing_migrations.js b/jstests/sharding/index_operations_abort_concurrent_outgoing_migrations.js
index 44ffd313d71..b65ae4af8bd 100644
--- a/jstests/sharding/index_operations_abort_concurrent_outgoing_migrations.js
+++ b/jstests/sharding/index_operations_abort_concurrent_outgoing_migrations.js
@@ -100,6 +100,38 @@ stepNames.forEach((stepName) => {
}
});
+if (!jsTestOptions().shardMixedBinVersions &&
+ !jsTest.options().useRandomBinVersionsWithinReplicaSet) {
+ stepNames.forEach((stepName) => {
+ jsTest.log(
+ `Testing that single phase createIndexes aborts concurrent outgoing migrations that are in step ${
+ stepName}...`);
+ const collName = "testSinglePhaseCreateIndexesMoveChunkStep" + stepName;
+ const ns = dbName + "." + collName;
+
+ assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: shardKey}));
+
+ assertCommandAbortsConcurrentOutgoingMigration(st, stepName, ns, () => {
+ const coll = st.s.getCollection(ns);
+
+ assert.commandWorked(coll.createIndexes([index]));
+ });
+
+ // Verify that the index command succeeds.
+ ShardedIndexUtil.assertIndexExistsOnShard(st.shard0, dbName, collName, index);
+
+ // If createIndexes is run after the migration has reached the steady state, shard1
+ // will not have the index created by the command because the index just does not
+ // exist when shard1 clones the collection options and indexes from shard0. However,
+ // if createIndexes is run after the cloning step starts but before the steady state
+ // is reached, shard0 may have the index when shard1 does the cloning so shard1 may
+ // or may not have the index.
+ if (stepName == moveChunkStepNames.reachedSteadyState) {
+ ShardedIndexUtil.assertIndexDoesNotExistOnShard(st.shard1, dbName, collName, index);
+ }
+ });
+}
+
stepNames.forEach((stepName) => {
jsTest.log(`Testing that dropIndexes aborts concurrent outgoing migrations that are in step ${
stepName}...`);
diff --git a/jstests/sharding/internal_txns/incomplete_transaction_history_during_migration.js b/jstests/sharding/internal_txns/incomplete_transaction_history_during_migration.js
new file mode 100644
index 00000000000..c9b85d2c426
--- /dev/null
+++ b/jstests/sharding/internal_txns/incomplete_transaction_history_during_migration.js
@@ -0,0 +1,89 @@
+/*
+ * Test that chunk migration can migrate a retryable internal transaction whose oplog entries have
+ * been truncated.
+ *
+ * @tags: [requires_fcv_60, uses_transactions, requires_persistence]
+ */
+(function() {
+'use strict';
+
+// This test involves writing directly to the config.transactions collection which is not allowed
+// in a session.
+TestData.disableImplicitSessions = true;
+
+const st = new ShardingTest({shards: 2, rs: {nodes: 1}});
+
+const dbName = 'testDb';
+const collName = 'testColl';
+const ns = dbName + '.' + collName;
+const testDB = st.s.getDB(dbName);
+
+assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
+st.ensurePrimaryShard(dbName, st.shard0.name);
+
+assert.commandWorked(st.s.getCollection(ns).createIndex({x: 1}));
+assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {x: 1}}));
+
+assert.commandWorked(st.s.getDB(dbName).runCommand({insert: collName, documents: [{x: 1}]}));
+
+const parentLsid = {
+ id: UUID()
+};
+const parentTxnNumber = NumberLong(35);
+
+const originalChildLsid = {
+ id: parentLsid.id,
+ txnNumber: parentTxnNumber,
+ txnUUID: UUID()
+};
+const childTxnNumber = NumberLong(1);
+
+const updateCmdObj = {
+ update: collName,
+ updates: [{q: {x: 1}, u: {$set: {y: 1}}}],
+ stmtId: NumberInt(0),
+};
+const res0 = assert.commandWorked(testDB.runCommand(Object.assign({}, updateCmdObj, {
+ lsid: originalChildLsid,
+ txnNumber: childTxnNumber,
+ autocommit: false,
+ startTransaction: true
+})));
+assert.eq(res0.nModified, 1, res0);
+assert.commandWorked(st.s.adminCommand({
+ commitTransaction: 1,
+ lsid: originalChildLsid,
+ txnNumber: childTxnNumber,
+ autocommit: false,
+}));
+
+// Manually update the config.transactions document for the retryable internal transaction to point
+// to an invalid op time.
+const shard0ConfigTxnsColl = st.rs0.getPrimary().getCollection("config.transactions");
+const res1 = assert.commandWorked(shard0ConfigTxnsColl.update(
+ {"_id.txnUUID": originalChildLsid.txnUUID},
+ {$set: {lastWriteOpTime: {ts: new Timestamp(100, 1), t: NumberLong(1)}}}));
+assert.eq(res1.nModified, 1, res1);
+
+assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: 0}}));
+assert.commandWorked(st.s.adminCommand({moveChunk: ns, find: {x: 0}, to: st.shard1.shardName}));
+
+assert.commandFailedWithCode(testDB.runCommand(Object.assign(
+ {}, updateCmdObj, {lsid: parentLsid, txnNumber: parentTxnNumber})),
+ ErrorCodes.IncompleteTransactionHistory);
+
+const retryChildLsid = {
+ id: parentLsid.id,
+ txnNumber: parentTxnNumber,
+ txnUUID: UUID()
+};
+assert.commandFailedWithCode(testDB.runCommand(Object.assign({}, updateCmdObj, {
+ lsid: retryChildLsid,
+ txnNumber: childTxnNumber,
+ autocommit: false,
+ startTransaction: true
+})),
+ ErrorCodes.IncompleteTransactionHistory);
+
+st.stop();
+})();
diff --git a/jstests/sharding/internal_txns/libs/chunk_migration_test.js b/jstests/sharding/internal_txns/libs/chunk_migration_test.js
index cc78170153e..a96b030890a 100644
--- a/jstests/sharding/internal_txns/libs/chunk_migration_test.js
+++ b/jstests/sharding/internal_txns/libs/chunk_migration_test.js
@@ -360,7 +360,7 @@ function InternalTransactionChunkMigrationTest(storeFindAndModifyImagesInSideCol
testCase.setUpFunc();
const lsid = getTransactionSessionId(txnType, testCase);
- runTxnRetryOnLockTimeoutError(() => {
+ runTxnRetryOnTransientError(() => {
const txnNumber = getNextTxnNumber(txnType, testCase);
for (let i = 0; i < testCase.commands.length; i++) {
@@ -401,7 +401,7 @@ function InternalTransactionChunkMigrationTest(storeFindAndModifyImagesInSideCol
const lsid = getTransactionSessionId(txnType, testCase);
// Give the session a different txnUUID to simulate a retry from a different mongos.
lsid.txnUUID = UUID();
- runTxnRetryOnLockTimeoutError(() => {
+ runTxnRetryOnTransientError(() => {
const txnNumber = getNextTxnNumber(txnType, testCase);
for (let i = 0; i < testCase.commands.length; i++) {
diff --git a/jstests/sharding/internal_txns/libs/fixture_helpers.js b/jstests/sharding/internal_txns/libs/fixture_helpers.js
index 5e418726fc6..8b215b98d14 100644
--- a/jstests/sharding/internal_txns/libs/fixture_helpers.js
+++ b/jstests/sharding/internal_txns/libs/fixture_helpers.js
@@ -5,28 +5,7 @@ function runTxnRetryOnTransientError(txnFunc) {
return true;
} catch (e) {
if (e.hasOwnProperty('errorLabels') &&
- e.errorLabels.includes('TransientTransactionError') &&
- e.code != ErrorCodes.NoSuchTransaction) {
- // Don't retry on a NoSuchTransaction error since it implies the transaction was
- // aborted so we should propagate the error instead.
- jsTest.log("Failed to run transaction due to a transient error " + tojson(e));
- return false;
- } else {
- throw e;
- }
- }
- });
-}
-
-function runTxnRetryOnLockTimeoutError(txnFunc) {
- assert.soon(() => {
- try {
- txnFunc();
- return true;
- } catch (e) {
- if (e.hasOwnProperty('errorLabels') &&
- e.errorLabels.includes('TransientTransactionError') &&
- e.code == ErrorCodes.LockTimeout) {
+ e.errorLabels.includes('TransientTransactionError')) {
jsTest.log("Failed to run transaction due to a transient error " + tojson(e));
return false;
} else {
diff --git a/jstests/sharding/internal_txns/libs/retryable_internal_transaction_test.js b/jstests/sharding/internal_txns/libs/retryable_internal_transaction_test.js
index 0332843f72c..1051bf1fc28 100644
--- a/jstests/sharding/internal_txns/libs/retryable_internal_transaction_test.js
+++ b/jstests/sharding/internal_txns/libs/retryable_internal_transaction_test.js
@@ -129,7 +129,7 @@ function RetryableInternalTransactionTest(collectionOptions = {}) {
// Initial try.
const initialLsid = txnOptions.makeSessionIdFunc();
let initialTxnNumber = 0;
- runTxnRetryOnLockTimeoutError(() => {
+ runTxnRetryOnTransientError(() => {
initialTxnNumber++;
setTxnFields(cmdObj, initialLsid, initialTxnNumber);
assert.commandWorked(mongosTestDB.runCommand(cmdObj));
@@ -175,7 +175,7 @@ function RetryableInternalTransactionTest(collectionOptions = {}) {
const initialLsid = txnOptions.makeSessionIdFunc();
let initialTxnNumber = 0;
let initialRes;
- runTxnRetryOnLockTimeoutError(() => {
+ runTxnRetryOnTransientError(() => {
initialTxnNumber++;
setTxnFields(cmdObj, initialLsid, initialTxnNumber);
initialRes = assert.commandWorked(mongosTestDB.runCommand(cmdObj));
@@ -202,7 +202,7 @@ function RetryableInternalTransactionTest(collectionOptions = {}) {
// different txnUUID) to simulate a retry from a different mongos.
const retryLsid = Object.assign({}, initialLsid, {txnUUID: UUID()});
let retryTxnNumber = 0;
- runTxnRetryOnLockTimeoutError(() => {
+ runTxnRetryOnTransientError(() => {
retryTxnNumber++;
setTxnFields(cmdObj, retryLsid, retryTxnNumber);
const retryRes = assert.commandWorked(mongosTestDB.runCommand(cmdObj));
@@ -271,7 +271,7 @@ function RetryableInternalTransactionTest(collectionOptions = {}) {
const initialLsid = txnOptions.makeSessionIdFunc();
let initialTxnNumber = 0;
let initialRes;
- runTxnRetryOnLockTimeoutError(() => {
+ runTxnRetryOnTransientError(() => {
initialTxnNumber++;
setTxnFields(cmdObjToRetry, initialLsid, initialTxnNumber);
insertCmdObjs.forEach(cmdObj => setTxnFields(cmdObj, initialLsid, initialTxnNumber));
@@ -314,7 +314,7 @@ function RetryableInternalTransactionTest(collectionOptions = {}) {
// different txnUUID) to simulate a retry from a different mongos.
const retryLsid = Object.assign({}, initialLsid, {txnUUID: UUID()});
let retryTxnNumber = 0;
- runTxnRetryOnLockTimeoutError(() => {
+ runTxnRetryOnTransientError(() => {
retryTxnNumber++;
setTxnFields(cmdObjToRetry, retryLsid, retryTxnNumber);
insertCmdObjs.forEach(cmdObj => setTxnFields(cmdObj, retryLsid, retryTxnNumber));
diff --git a/jstests/sharding/internal_txns/retryable_findAndModify_commit_and_abort_prepared_txns_after_failover_and_restart.js b/jstests/sharding/internal_txns/retryable_findAndModify_commit_and_abort_prepared_txns_after_failover_and_restart.js
index 27cf8aef667..49d90c683b0 100644
--- a/jstests/sharding/internal_txns/retryable_findAndModify_commit_and_abort_prepared_txns_after_failover_and_restart.js
+++ b/jstests/sharding/internal_txns/retryable_findAndModify_commit_and_abort_prepared_txns_after_failover_and_restart.js
@@ -184,15 +184,15 @@ function runTest(st, stepDownShard0PrimaryFunc, testOpts = {
}
{
- jsTest.log("Test when the old primary restarts");
- const st = new ShardingTest({shards: 1, rs: {nodes: 1}});
+ jsTest.log("Test when a participant shard restarts");
+ const st = new ShardingTest({shards: 1, rs: {nodes: 2}});
const restartShard0Func = () => {
st.rs0.stopSet(null /* signal */, true /*forRestart */);
st.rs0.startSet({restart: true});
st.rs0.getPrimary();
- // Wait for replication since it is illegal to run commitTransaction before the prepare
- // oplog entry has been majority committed.
- st.rs0.awaitReplication();
+ // Wait for replication to recover the lastCommittedOpTime since it is illegal to run
+ // commitTransaction before the prepare oplog entry has been majority committed.
+ st.rs0.awaitLastOpCommitted();
};
// Test findAnModify without pre/post image.
diff --git a/jstests/sharding/jumbo1.js b/jstests/sharding/jumbo1.js
deleted file mode 100644
index fa5d13b9f2a..00000000000
--- a/jstests/sharding/jumbo1.js
+++ /dev/null
@@ -1,46 +0,0 @@
-(function() {
-'use strict';
-
-load("jstests/sharding/libs/find_chunks_util.js");
-
-var s = new ShardingTest({shards: 2, other: {chunkSize: 1}});
-
-assert.commandWorked(s.s.adminCommand({enablesharding: "test", primaryShard: s.shard1.shardName}));
-assert.commandWorked(
- s.s.adminCommand({addShardToZone: s.shard0.shardName, zone: 'finalDestination'}));
-
-// Set the chunk range with a zone that will cause the chunk to be in the wrong place so the
-// balancer will be forced to attempt to move it out.
-assert.commandWorked(s.s.adminCommand({shardcollection: "test.foo", key: {x: 1}}));
-assert.commandWorked(s.s.adminCommand(
- {updateZoneKeyRange: 'test.foo', min: {x: 0}, max: {x: MaxKey}, zone: 'finalDestination'}));
-
-var db = s.getDB("test");
-
-const big = 'X'.repeat(1024 * 1024); // 1MB
-
-// Insert 3MB of documents to create a jumbo chunk, and use the same shard key in all of
-// them so that the chunk cannot be split.
-var bulk = db.foo.initializeUnorderedBulkOp();
-for (var i = 0; i < 3; i++) {
- bulk.insert({x: 0, big: big});
-}
-
-assert.commandWorked(bulk.execute());
-
-s.startBalancer();
-
-// Wait for the balancer to try to move the chunk and mark it as jumbo.
-assert.soon(() => {
- let chunk = findChunksUtil.findOneChunkByNs(s.getDB('config'), 'test.foo', {min: {x: 0}});
- if (chunk == null) {
- // Balancer hasn't run and enforce the zone boundaries yet.
- return false;
- }
-
- assert.eq(s.shard1.shardName, chunk.shard, `${tojson(chunk)} was moved by the balancer`);
- return chunk.jumbo;
-});
-
-s.stop();
-})();
diff --git a/jstests/sharding/jumbo_chunks.js b/jstests/sharding/jumbo_chunks.js
new file mode 100644
index 00000000000..4e03817b68e
--- /dev/null
+++ b/jstests/sharding/jumbo_chunks.js
@@ -0,0 +1,178 @@
+/**
+ * Test jumbo chunks
+ *
+ * @tags: [
+ * # Command `configureCollectionBalancing` was added in v5.3
+ * requires_fcv_53,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+load("jstests/sharding/libs/find_chunks_util.js");
+
+function bulkInsert(coll, keyValue, sizeMBytes) {
+ const big = 'X'.repeat(1024 * 1024); // 1MB
+ var bulk = coll.initializeUnorderedBulkOp();
+ for (var i = 0; i < sizeMBytes; i++) {
+ bulk.insert({x: keyValue, big: big});
+ }
+ assert.commandWorked(bulk.execute());
+}
+
+function assertNumJumboChunks(configDB, ns, expectedNumJumboChunks) {
+ assert.eq(findChunksUtil.countChunksForNs(configDB, ns, {jumbo: true}), expectedNumJumboChunks);
+}
+
+function setGlobalChunkSize(st, chunkSizeMBytes) {
+ // Set global chunk size
+ assert.commandWorked(
+ st.s.getDB("config").settings.update({_id: 'chunksize'},
+ {$set: {value: chunkSizeMBytes}},
+ {upsert: true, writeConcern: {w: 'majority'}}));
+}
+
+function setCollectionChunkSize(st, ns, chunkSizeMBytes) {
+ assert.commandWorked(
+ st.s.adminCommand({configureCollectionBalancing: ns, chunkSize: chunkSizeMBytes}));
+}
+
+// Test setup
+var st = new ShardingTest({shards: 2, other: {chunkSize: 1}});
+
+assert.commandWorked(
+ st.s.adminCommand({enablesharding: "test", primaryShard: st.shard1.shardName}));
+assert.commandWorked(st.s.adminCommand({addShardToZone: st.shard0.shardName, zone: 'zoneShard0'}));
+
+// Try to move unsuccessfully a 3MB chunk and check it gets marked as jumbo
+{
+ // Set the chunk range with a zone that will cause the chunk to be in the wrong place so the
+ // balancer will be forced to attempt to move it out.
+ assert.commandWorked(st.s.adminCommand({shardcollection: "test.foo", key: {x: 1}}));
+ assert.commandWorked(st.s.adminCommand(
+ {updateZoneKeyRange: 'test.foo', min: {x: 0}, max: {x: MaxKey}, zone: 'zoneShard0'}));
+
+ var db = st.getDB("test");
+
+ const big = 'X'.repeat(1024 * 1024); // 1MB
+
+ // Insert 3MB of documents to create a jumbo chunk, and use the same shard key in all of
+ // them so that the chunk cannot be split.
+ var bulk = db.foo.initializeUnorderedBulkOp();
+ for (var i = 0; i < 3; i++) {
+ bulk.insert({x: 0, big: big});
+ }
+
+ assert.commandWorked(bulk.execute());
+
+ st.startBalancer();
+
+ // Wait for the balancer to try to move the chunk and check it gets marked as jumbo.
+ assert.soon(() => {
+ let chunk = findChunksUtil.findOneChunkByNs(st.getDB('config'), 'test.foo', {min: {x: 0}});
+ if (chunk == null) {
+ // Balancer hasn't run and enforce the zone boundaries yet.
+ return false;
+ }
+
+ assert.eq(st.shard1.shardName, chunk.shard, `${tojson(chunk)} was moved by the balancer`);
+ return chunk.jumbo;
+ });
+
+ st.stopBalancer();
+}
+
+// Move successfully a 3MB chunk
+// Collection chunkSize must prevail over global chunkSize setting
+// global chunkSize -> 1MB
+// collection chunkSize -> 5MB
+{
+ const collName = "collA";
+ const coll = st.s.getDB("test").getCollection(collName);
+ const configDB = st.s.getDB("config");
+ const splitPoint = 0;
+
+ assert.commandWorked(st.s.adminCommand({shardcollection: coll.getFullName(), key: {x: 1}}));
+ assert.commandWorked(st.s.adminCommand({
+ updateZoneKeyRange: coll.getFullName(),
+ min: {x: splitPoint},
+ max: {x: MaxKey},
+ zone: 'zoneShard0'
+ }));
+
+ bulkInsert(coll, splitPoint, 3);
+
+ setCollectionChunkSize(st, coll.getFullName(), 5);
+ setGlobalChunkSize(st, 1);
+
+ // Move the 3MB chunk to shard0
+ st.startBalancer();
+ st.awaitCollectionBalance(coll);
+ st.stopBalancer();
+
+ const chunk =
+ findChunksUtil.findOneChunkByNs(configDB, coll.getFullName(), {min: {x: splitPoint}});
+
+ // Verify chunk has been moved to shard0
+ assert.eq(st.shard0.shardName,
+ chunk.shard,
+ `${tojson(chunk)} was not moved to ${tojson(st.shard0.shardName)}`);
+ assertNumJumboChunks(configDB, coll.getFullName(), 0);
+
+ coll.drop();
+}
+
+// Try to move unsuccessfully a 3MB chunk and mark it as jumbo
+// Collection chunkSize must prevail over global chunkSize setting
+// global chunkSize -> 5MB
+// collection chunkSize -> 1MB
+{
+ const collName = "collB";
+ const coll = st.s.getDB("test").getCollection(collName);
+ const configDB = st.s.getDB("config");
+ const splitPoint = 0;
+
+ assert.commandWorked(st.s.adminCommand({shardcollection: coll.getFullName(), key: {x: 1}}));
+ assert.commandWorked(st.s.adminCommand({
+ updateZoneKeyRange: coll.getFullName(),
+ min: {x: splitPoint},
+ max: {x: MaxKey},
+ zone: 'zoneShard0'
+ }));
+
+ bulkInsert(coll, splitPoint, 3);
+
+ setCollectionChunkSize(st, coll.getFullName(), 1);
+ setGlobalChunkSize(st, 5);
+
+ // Try to move the 3MB chunk and mark it as jumbo
+ st.startBalancer();
+
+ assert.soon(() => {
+ const chunk =
+ findChunksUtil.findOneChunkByNs(configDB, coll.getFullName(), {min: {x: splitPoint}});
+ if (chunk == null) {
+ // Balancer hasn't run and enforce the zone boundaries yet.
+ return false;
+ }
+
+ return chunk.jumbo;
+ });
+
+ st.stopBalancer();
+
+ const chunk =
+ findChunksUtil.findOneChunkByNs(configDB, coll.getFullName(), {min: {x: splitPoint}});
+
+ // Verify chunk hasn't been moved to shard0 and it's jumbo
+ assert.eq(st.shard1.shardName,
+ chunk.shard,
+ `${tojson(chunk)} was moved to ${tojson(st.shard0.shardName)}`);
+ assertNumJumboChunks(configDB, coll.getFullName(), 1);
+
+ coll.drop();
+}
+
+st.stop();
+})();
diff --git a/jstests/sharding/libs/defragmentation_util.js b/jstests/sharding/libs/defragmentation_util.js
index 52ac1333ef7..943f2eb462d 100644
--- a/jstests/sharding/libs/defragmentation_util.js
+++ b/jstests/sharding/libs/defragmentation_util.js
@@ -21,7 +21,9 @@ var defragmentationUtil = (function() {
}
createAndDistributeChunks(mongos, ns, numChunks, chunkSpacing);
- createRandomZones(mongos, ns, numZones, chunkSpacing);
+ // Created zones will line up exactly with existing chunks so as not to trigger zone
+ // violations in the balancer.
+ createRandomZones(mongos, ns, numZones);
fillChunksToRandomSize(mongos, ns, docSizeBytes, maxChunkFillMB);
const beginningNumberChunks = findChunksUtil.countChunksForNs(mongos.getDB('config'), ns);
@@ -49,19 +51,18 @@ var defragmentationUtil = (function() {
}
};
- let createRandomZones = function(mongos, ns, numZones, chunkSpacing) {
- for (let i = -Math.floor(numZones / 2); i < Math.ceil(numZones / 2); i++) {
+ let createRandomZones = function(mongos, ns, numZones) {
+ let existingChunks = findChunksUtil.findChunksByNs(mongos.getDB('config'), ns);
+ existingChunks = Array.shuffle(existingChunks.toArray());
+ for (let i = 0; i < numZones; i++) {
let zoneName = "Zone" + i;
- let shardForZone =
- findChunksUtil
- .findOneChunkByNs(mongos.getDB('config'), ns, {min: {key: i * chunkSpacing}})
- .shard;
+ let shardForZone = existingChunks[i].shard;
assert.commandWorked(
mongos.adminCommand({addShardToZone: shardForZone, zone: zoneName}));
assert.commandWorked(mongos.adminCommand({
updateZoneKeyRange: ns,
- min: {key: i * chunkSpacing},
- max: {key: i * chunkSpacing + chunkSpacing},
+ min: existingChunks[i].min,
+ max: existingChunks[i].max,
zone: zoneName
}));
}
@@ -202,6 +203,13 @@ var defragmentationUtil = (function() {
assert.soon(function() {
let balancerStatus =
assert.commandWorked(mongos.adminCommand({balancerCollectionStatus: ns}));
+
+ if (balancerStatus.balancerCompliant) {
+ // As we can't rely on `balancerCompliant` due to orphan counter non atomic update,
+ // we need to ensure the collection is balanced by some extra checks
+ sh.awaitCollectionBalance(mongos.getCollection(ns));
+ }
+
return balancerStatus.balancerCompliant ||
balancerStatus.firstComplianceViolation !== 'defragmentingChunks';
});
diff --git a/jstests/sharding/libs/last_lts_mongod_commands.js b/jstests/sharding/libs/last_lts_mongod_commands.js
index c25af3a8275..f8c0802af04 100644
--- a/jstests/sharding/libs/last_lts_mongod_commands.js
+++ b/jstests/sharding/libs/last_lts_mongod_commands.js
@@ -20,8 +20,12 @@ const commandsAddedToMongodSinceLastLTS = [
"clusterGetMore",
"clusterInsert",
"clusterUpdate",
+ "createSearchIndexes",
+ "dropSearchIndex",
"getClusterParameter",
+ "listSearchIndexes",
"rotateCertificates",
"setClusterParameter",
"setUserWriteBlockMode",
+ "updateSearchIndex",
];
diff --git a/jstests/sharding/libs/last_lts_mongos_commands.js b/jstests/sharding/libs/last_lts_mongos_commands.js
index f7a72743e6b..c2d8308e5f4 100644
--- a/jstests/sharding/libs/last_lts_mongos_commands.js
+++ b/jstests/sharding/libs/last_lts_mongos_commands.js
@@ -18,12 +18,16 @@ const commandsAddedToMongosSinceLastLTS = [
"commitReshardCollection",
"compactStructuredEncryptionData",
"configureCollectionBalancing",
+ "createSearchIndexes",
+ "dropSearchIndex",
"getClusterParameter",
+ "listSearchIndexes",
"moveRange",
"reshardCollection",
"rotateCertificates",
"setAllowMigrations",
"setClusterParameter",
+ "setProfilingFilterGlobally", // TODO SERVER-73305
"setUserWriteBlockMode",
"testDeprecation",
"testDeprecationInVersion2",
@@ -31,4 +35,5 @@ const commandsAddedToMongosSinceLastLTS = [
"testRemoval",
"testVersions1And2",
"testVersion2",
+ "updateSearchIndex",
];
diff --git a/jstests/sharding/libs/mongos_api_params_util.js b/jstests/sharding/libs/mongos_api_params_util.js
index e1ca18df3e6..8289582d9ee 100644
--- a/jstests/sharding/libs/mongos_api_params_util.js
+++ b/jstests/sharding/libs/mongos_api_params_util.js
@@ -75,7 +75,7 @@ let MongosAPIParametersUtil = (function() {
function awaitRemoveShard(shardName) {
assert.commandWorked(st.startBalancer());
- st.waitForBalancer(true, 60000);
+ st.awaitBalancerRound();
assert.soon(() => {
const res = st.s.adminCommand({removeShard: shardName});
jsTestLog(`removeShard result: ${tojson(res)}`);
@@ -1215,9 +1215,8 @@ let MongosAPIParametersUtil = (function() {
}
},
{
- commandName: "setFreeMonitoring",
- skip: "explicitly fails for mongos, primary mongod only",
- conditional: true
+ commandName: "setProfilingFilterGlobally",
+ skip: "executes locally on mongos (not sent to any remote node)",
},
{
commandName: "setParameter",
diff --git a/jstests/sharding/libs/resharding_test_fixture.js b/jstests/sharding/libs/resharding_test_fixture.js
index 13d39674f0f..cd658ae9508 100644
--- a/jstests/sharding/libs/resharding_test_fixture.js
+++ b/jstests/sharding/libs/resharding_test_fixture.js
@@ -85,11 +85,11 @@ var ReshardingTest = class {
/** @private */
this._newShardKey = undefined;
/** @private */
- this._pauseCoordinatorBeforeBlockingWrites = undefined;
+ this._pauseCoordinatorBeforeBlockingWritesFailpoints = [];
/** @private */
- this._pauseCoordinatorBeforeDecisionPersistedFailpoint = undefined;
+ this._pauseCoordinatorBeforeDecisionPersistedFailpoints = [];
/** @private */
- this._pauseCoordinatorBeforeCompletionFailpoint = undefined;
+ this._pauseCoordinatorBeforeCompletionFailpoints = [];
/** @private */
this._reshardingThread = undefined;
/** @private */
@@ -283,6 +283,11 @@ var ReshardingTest = class {
return sourceCollection;
}
+ get tempNs() {
+ assert.neq(undefined, this._tempNs, "createShardedCollection must be called first");
+ return this._tempNs;
+ }
+
/**
* Reshards an existing collection using the specified new shard key and new chunk ranges.
*
@@ -316,13 +321,19 @@ var ReshardingTest = class {
this._newShardKey = Object.assign({}, newShardKeyPattern);
- const configPrimary = this._st.configRS.getPrimary();
- this._pauseCoordinatorBeforeBlockingWrites =
- configureFailPoint(configPrimary, "reshardingPauseCoordinatorBeforeBlockingWrites");
- this._pauseCoordinatorBeforeDecisionPersistedFailpoint =
- configureFailPoint(configPrimary, "reshardingPauseCoordinatorBeforeDecisionPersisted");
- this._pauseCoordinatorBeforeCompletionFailpoint = configureFailPoint(
- configPrimary, "reshardingPauseCoordinatorBeforeCompletion", {}, {times: 1});
+ this._pauseCoordinatorBeforeBlockingWritesFailpoints = [];
+ this._pauseCoordinatorBeforeDecisionPersistedFailpoints = [];
+ this._pauseCoordinatorBeforeCompletionFailpoints = [];
+ this._st.forEachConfigServer((configServer) => {
+ this._pauseCoordinatorBeforeBlockingWritesFailpoints.push(
+ configureFailPoint(configServer, "reshardingPauseCoordinatorBeforeBlockingWrites"));
+ this._pauseCoordinatorBeforeDecisionPersistedFailpoints.push(configureFailPoint(
+ configServer, "reshardingPauseCoordinatorBeforeDecisionPersisted"));
+ this._pauseCoordinatorBeforeCompletionFailpoints.push(
+ configureFailPoint(configServer,
+ "reshardingPauseCoordinatorBeforeCompletion",
+ {"sourceNamespace": this._ns}));
+ });
this._commandDoneSignal = new CountDownLatch(1);
@@ -452,9 +463,9 @@ var ReshardingTest = class {
try {
fn();
} catch (duringReshardingError) {
- for (const fp of [this._pauseCoordinatorBeforeBlockingWrites,
- this._pauseCoordinatorBeforeDecisionPersistedFailpoint,
- this._pauseCoordinatorBeforeCompletionFailpoint]) {
+ for (const fp of [...this._pauseCoordinatorBeforeBlockingWritesFailpoints,
+ ...this._pauseCoordinatorBeforeDecisionPersistedFailpoints,
+ ...this._pauseCoordinatorBeforeCompletionFailpoints]) {
try {
fp.off();
} catch (disableFailpointError) {
@@ -503,7 +514,9 @@ var ReshardingTest = class {
* proceeding to the next stage. This helper returns after either:
*
* 1) The node's waitForFailPoint returns successfully or
- * 2) The `reshardCollection` command has returned a response.
+ * 2) The `reshardCollection` command has returned a response or
+ * 3) The ReshardingCoordinator is blocked on the reshardingPauseCoordinatorBeforeCompletion
+ * failpoint and won't ever satisfy the supplied failpoint.
*
* The function returns true when we returned because the server reached the failpoint. The
* function returns false when the `reshardCollection` command is no longer running.
@@ -512,9 +525,20 @@ var ReshardingTest = class {
* @private
*/
_waitForFailPoint(fp) {
+ const completionFailpoint = this._pauseCoordinatorBeforeCompletionFailpoints.find(
+ completionFailpoint => completionFailpoint.conn.host === fp.conn.host);
+
assert.soon(
() => {
- return this._commandDoneSignal.getCount() === 0 || fp.waitWithTimeout(1000);
+ if (this._commandDoneSignal.getCount() === 0 || fp.waitWithTimeout(1000)) {
+ return true;
+ }
+
+ if (completionFailpoint !== fp && completionFailpoint.waitWithTimeout(1000)) {
+ completionFailpoint.off();
+ }
+
+ return false;
},
"Timed out waiting for failpoint to be hit. Failpoint: " + fp.failPointName,
undefined,
@@ -529,6 +553,19 @@ var ReshardingTest = class {
postCheckConsistencyFn = () => {},
postDecisionPersistedFn = () => {},
afterReshardingFn = () => {}) {
+ // The CSRS primary may have changed as a result of running the duringReshardingFn()
+ // callback function. The failpoints will only be triggered on the new CSRS primary so we
+ // detect which node that is here.
+ const configPrimary = this._st.configRS.getPrimary();
+ const primaryIdx = this._pauseCoordinatorBeforeBlockingWritesFailpoints.findIndex(
+ fp => fp.conn.host === configPrimary.host);
+ // The CSRS secondaries may be going through replication rollback which closes their
+ // connections to the test client. We wait for any replication rollbacks to complete and for
+ // the test client to have reconnected so the failpoints can be turned off on all of the
+ // nodes later on.
+ this._st.configRS.awaitSecondaryNodes();
+ this._st.configRS.awaitReplication();
+
let performCorrectnessChecks = true;
if (expectedErrorCode === ErrorCodes.OK) {
this._callFunctionSafely(() => {
@@ -539,17 +576,18 @@ var ReshardingTest = class {
// reshardingPauseCoordinatorBeforeDecisionPersisted failpoint to wait for all of
// the recipient shards to have applied through all of the oplog entries from all of
// the donor shards.
- if (!this._waitForFailPoint(this._pauseCoordinatorBeforeBlockingWrites)) {
+ if (!this._waitForFailPoint(
+ this._pauseCoordinatorBeforeBlockingWritesFailpoints[primaryIdx])) {
performCorrectnessChecks = false;
}
- this._pauseCoordinatorBeforeBlockingWrites.off();
+ this._pauseCoordinatorBeforeBlockingWritesFailpoints.forEach(fp => fp.off());
// A resharding command that returned a failure will not hit the "Decision
// Persisted" failpoint. If the command has returned, don't require that the
// failpoint was entered. This ensures that following up by joining the
// `_reshardingThread` will succeed.
if (!this._waitForFailPoint(
- this._pauseCoordinatorBeforeDecisionPersistedFailpoint)) {
+ this._pauseCoordinatorBeforeDecisionPersistedFailpoints[primaryIdx])) {
performCorrectnessChecks = false;
}
@@ -562,22 +600,21 @@ var ReshardingTest = class {
postCheckConsistencyFn();
}
- this._pauseCoordinatorBeforeDecisionPersistedFailpoint.off();
+ this._pauseCoordinatorBeforeDecisionPersistedFailpoints.forEach(fp => fp.off());
postDecisionPersistedFn();
- this._pauseCoordinatorBeforeCompletionFailpoint.off();
+ this._pauseCoordinatorBeforeCompletionFailpoints.forEach(fp => fp.off());
});
} else {
this._callFunctionSafely(() => {
- this.retryOnceOnNetworkError( //
- () => this._pauseCoordinatorBeforeBlockingWrites.off());
-
+ this._pauseCoordinatorBeforeBlockingWritesFailpoints.forEach(
+ fp => this.retryOnceOnNetworkError(fp.off));
postCheckConsistencyFn();
- this.retryOnceOnNetworkError(
- () => this._pauseCoordinatorBeforeDecisionPersistedFailpoint.off());
+ this._pauseCoordinatorBeforeDecisionPersistedFailpoints.forEach(
+ fp => this.retryOnceOnNetworkError(fp.off));
postDecisionPersistedFn();
- this.retryOnceOnNetworkError(
- () => this._pauseCoordinatorBeforeCompletionFailpoint.off());
+ this._pauseCoordinatorBeforeCompletionFailpoints.forEach(
+ fp => this.retryOnceOnNetworkError(fp.off));
});
}
@@ -600,7 +637,11 @@ var ReshardingTest = class {
/** @private */
_checkConsistency() {
- const nsCursor = this._st.s.getCollection(this._ns).find().sort({_id: 1});
+ // The "available" read concern level won't block this find cmd behind the critical section.
+ // Tests for resharding are not expected to have unowned documents in the collection being
+ // resharded.
+ const nsCursor =
+ this._st.s.getCollection(this._ns).find().readConcern("available").sort({_id: 1});
const tempNsCursor = this._st.s.getCollection(this._tempNs).find().sort({_id: 1});
const diff = ((diff) => {
@@ -618,7 +659,8 @@ var ReshardingTest = class {
docsExtraAfterResharding: [],
docsMissingAfterResharding: [],
},
- "existing sharded collection and temporary resharding collection had different" +
+ "existing sharded collection " + this._ns +
+ " and temporary resharding collection " + this._tempNs + " had different" +
" contents");
}
diff --git a/jstests/sharding/libs/with_partial_shard_key_util.js b/jstests/sharding/libs/with_partial_shard_key_util.js
new file mode 100644
index 00000000000..bcfeae08926
--- /dev/null
+++ b/jstests/sharding/libs/with_partial_shard_key_util.js
@@ -0,0 +1,28 @@
+/**
+ * Utilities for the find_and_modify_with_partial_shard_key.js and delete_with_partial_shard_key.js
+ * tests.
+ */
+"use strict";
+
+/**
+ * Runs an explain on the `cmdObj` and checks that the explain targets the shard given be
+ * `expectedShardName`.
+ */
+function assertExplainTargetsCorrectShard(db, cmdObj, expectedShardName) {
+ var res = db.runCommand({explain: cmdObj});
+ assert.eq(res.queryPlanner.winningPlan.shards.length, 1);
+ assert.eq(res.queryPlanner.winningPlan.shards[0].shardName, expectedShardName);
+}
+
+/**
+ * Performs a split given by the `splitDoc`, and then moves the chunk containg `moveShard0Doc` to
+ * shard0 and the chunk containing `moveShard1Doc` to shard1.
+ */
+function splitAndMoveChunks(st, splitDoc, moveShard0Doc, moveShard1Doc) {
+ assert.commandWorked(st.s0.adminCommand({split: "test.sharded_coll", middle: splitDoc}));
+
+ assert.commandWorked(st.s0.adminCommand(
+ {moveChunk: "test.sharded_coll", find: moveShard0Doc, to: st.shard0.shardName}));
+ assert.commandWorked(st.s0.adminCommand(
+ {moveChunk: "test.sharded_coll", find: moveShard1Doc, to: st.shard1.shardName}));
+}
diff --git a/jstests/sharding/log_remote_op_wait.js b/jstests/sharding/log_remote_op_wait.js
index 78241bf53c9..c514b211261 100644
--- a/jstests/sharding/log_remote_op_wait.js
+++ b/jstests/sharding/log_remote_op_wait.js
@@ -130,30 +130,6 @@ assert(!csCursor.hasNext());
assert.gte(remoteOpWait, 900);
}
-// An equivalent .find() does not include remoteOpWaitMillis.
-const findComment = 'example_find_should_not_have_remote_op_wait';
-coll.find().sort({x: 1}).comment(findComment).next();
-{
- const mongosLog = assert.commandWorked(st.s.adminCommand({getLog: "global"}));
- const lines =
- [...findMatchingLogLines(mongosLog.log, {msg: "Slow query", comment: findComment})];
- const line = lines.find(line => line.match(/command.{1,4}find/));
- assert(line, "Failed to find a 'find' log line matching the comment");
- assert(!line.match(/remoteOpWait/), `Log line unexpectedly contained remoteOpWait: ${line}`);
-}
-
-// Other cursor-producing commands do not include remoteOpWaitMillis, such as listCollections.
-const listCollectionsComment = 'example_listCollections_should_not_have_remote_op_wait';
-coll.runCommand({listCollections: 1, comment: listCollectionsComment});
-{
- const mongosLog = assert.commandWorked(st.s.adminCommand({getLog: "global"}));
- const lines = [...findMatchingLogLines(mongosLog.log,
- {msg: "Slow query", comment: listCollectionsComment})];
- const line = lines.find(line => line.match(/command.{1,4}listCollections/));
- assert(line, "Failed to find a 'listCollections' log line matching the comment");
- assert(!line.match(/remoteOpWait/), `Log line unexpectedly contained remoteOpWait: ${line}`);
-}
-
// A query that merges on a shard logs remoteOpWaitMillis on the shard.
const pipeline2 = [{$sort: {x: 1}}, {$group: {_id: "$y"}}];
const pipelineComment2 = 'example_pipeline2_should_have_remote_op_wait';
diff --git a/jstests/sharding/log_remote_op_wait_for_other_commands.js b/jstests/sharding/log_remote_op_wait_for_other_commands.js
new file mode 100644
index 00000000000..a8793d790ce
--- /dev/null
+++ b/jstests/sharding/log_remote_op_wait_for_other_commands.js
@@ -0,0 +1,89 @@
+/**
+ * Tests that command log lines which execute remote operations include remoteOpWaitMillis: the
+ * amount of time the merger spent waiting for results from shards.
+ *
+ * @tags: [
+ * # The 'remoteOpWaitMillis' was added to explain output for commands other than $mergeCursor
+ * # aggregation stage in 6.0.
+ * requires_fcv_60
+ * ]
+ */
+(function() {
+
+load("jstests/libs/log.js"); // For findMatchingLogLine.
+
+const st = new ShardingTest({shards: 2, rs: {nodes: 1}});
+st.stopBalancer();
+
+const dbName = st.s.defaultDB;
+const coll = st.s.getDB(dbName).getCollection('profile_remote_op_wait');
+
+coll.drop();
+assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
+
+// Shards the test collection and splits it into two chunks: one that contains all {shard: 1}
+// documents and one that contains all {shard: 2} documents.
+st.shardColl(coll.getName(),
+ {shard: 1} /* shard key */,
+ {shard: 2} /* split at */,
+ {shard: 2} /* move the chunk containing {shard: 2} to its own shard */,
+ dbName,
+ true);
+
+assert.commandWorked(
+ coll.insert(Array.from({length: 100}, (_, i) => ({_id: i, shard: (i % 2) + 1, x: i}))));
+
+// Sets the slow query logging threshold (slowMS) to -1 to ensure every query gets logged.
+st.s.getDB('admin').setProfilingLevel(0, -1);
+
+function getRemoteOpWait(logLine) {
+ const pattern = /remoteOpWaitMillis"?:([0-9]+)/;
+ const match = logLine.match(pattern);
+ assert(match, `pattern ${pattern} did not match line: ${logLine}`);
+ const millis = parseInt(match[1]);
+ assert.gte(millis, 0, match);
+ return millis;
+}
+
+function getDuration(logLine) {
+ const pattern = /durationMillis"?:([0-9]+)/;
+ const match = logLine.match(pattern);
+ assert(match, `pattern ${pattern} did not match line: ${logLine}`);
+ const millis = parseInt(match[1]);
+ assert.gte(millis, 0, match);
+ return millis;
+}
+
+// An .find() includes remoteOpWaitMillis.
+const findComment = 'example_find_should_have_remote_op_wait_too';
+coll.find().sort({x: 1}).comment(findComment).next();
+{
+ const mongosLog = assert.commandWorked(st.s.adminCommand({getLog: "global"}));
+ const lines =
+ [...findMatchingLogLines(mongosLog.log, {msg: "Slow query", comment: findComment})];
+ const line = lines.find(line => line.match(/command.{1,4}find/));
+ assert(line, "Failed to find a 'find' log line matching the comment");
+ assert(line.match(/remoteOpWait/), `Log line does not contain remoteOpWait: ${line}`);
+ const remoteOpWait = getRemoteOpWait(line);
+ const duration = getDuration(line);
+ assert.lte(remoteOpWait, duration);
+}
+
+// Other commands which execute remote operations also include remoteOpWaitMillis, such as
+// listCollections.
+const listCollectionsComment = 'example_listCollections_should_have_remote_op_wait_too';
+coll.runCommand({listCollections: 1, comment: listCollectionsComment});
+{
+ const mongosLog = assert.commandWorked(st.s.adminCommand({getLog: "global"}));
+ const lines = [...findMatchingLogLines(mongosLog.log,
+ {msg: "Slow query", comment: listCollectionsComment})];
+ const line = lines.find(line => line.match(/command.{1,4}listCollections/));
+ assert(line, "Failed to find a 'listCollections' log line matching the comment");
+ assert(line.match(/remoteOpWait/), `Log line does not contain remoteOpWait: ${line}`);
+ const remoteOpWait = getRemoteOpWait(line);
+ const duration = getDuration(line);
+ assert.lte(remoteOpWait, duration);
+}
+
+st.stop();
+})();
diff --git a/jstests/sharding/max_time_ms_does_not_leak_shard_cursor.js b/jstests/sharding/max_time_ms_does_not_leak_shard_cursor.js
deleted file mode 100644
index 0c0d67b9554..00000000000
--- a/jstests/sharding/max_time_ms_does_not_leak_shard_cursor.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// Tests that if a mongoS cursor exceeds the maxTimeMs timeout, the cursors on the shards will be
-// cleaned up. Exercises the fix for the bug described in SERVER-62710.
-//
-// @tags: []
-
-(function() {
-"use strict";
-
-load("jstests/libs/fail_point_util.js"); // for 'configureFailPoint()'
-
-function getIdleCursors(conn, collName) {
- return conn.getDB('admin')
- .aggregate([
- {$currentOp: {idleCursors: true}},
- {$match: {$and: [{type: "idleCursor"}, {"cursor.originatingCommand.find": collName}]}}
- ])
- .toArray();
-}
-
-function assertNoIdleCursors(conn, collName) {
- const sleepTimeMS = 10 * 1000;
- const retries = 2;
- assert.soon(() => {
- return getIdleCursors(conn, collName).length === 0;
- }, tojson(getIdleCursors(conn, collName)), retries * sleepTimeMS, sleepTimeMS, {
- runHangAnalyzer: false
- });
-}
-
-const st = new ShardingTest({shards: 1, mongos: 1, config: 1});
-
-const dbName = "test";
-const collName = jsTestName();
-
-const coll = st.s.getCollection(dbName + "." + collName);
-
-assert.commandWorked(coll.insert(Array.from({length: 1000}, _ => ({a: 1}))));
-
-// Perform a query that sleeps after retrieving each document.
-// This is guaranteed to exceed the specified maxTimeMS limit.
-// The timeout may happen either on mongoS or on shard.
-const curs = coll.find({
- $where: function() {
- sleep(1);
- return true;
- }
- })
- .batchSize(2)
- .maxTimeMS(100);
-assert.eq(getIdleCursors(st.shard0, collName).length, 0);
-assert.throwsWithCode(() => {
- curs.itcount();
-}, ErrorCodes.MaxTimeMSExpired);
-assertNoIdleCursors(st.shard0, collName);
-
-// Ensure the timeout happens on mongoS.
-const cursTestMongoS = coll.find({}).batchSize(2).maxTimeMS(100);
-const fpTestMongoS = configureFailPoint(st.s, "maxTimeAlwaysTimeOut", {}, "alwaysOn");
-assert.throwsWithCode(() => {
- cursTestMongoS.itcount();
-}, ErrorCodes.MaxTimeMSExpired);
-fpTestMongoS.off();
-assertNoIdleCursors(st.shard0, collName);
-
-// Ensure the timeout happens on the shard.
-const cursTestShard0 = coll.find({}).batchSize(2).maxTimeMS(100);
-const fpTestShard0 = configureFailPoint(st.shard0, "maxTimeAlwaysTimeOut", {}, "alwaysOn");
-assert.throwsWithCode(() => {
- cursTestShard0.itcount();
-}, ErrorCodes.MaxTimeMSExpired);
-fpTestShard0.off();
-assertNoIdleCursors(st.shard0, collName);
-
-st.stop();
-})();
diff --git a/jstests/sharding/merge_let_params_size_estimation.js b/jstests/sharding/merge_let_params_size_estimation.js
new file mode 100644
index 00000000000..66f30d38335
--- /dev/null
+++ b/jstests/sharding/merge_let_params_size_estimation.js
@@ -0,0 +1,155 @@
+/**
+ * Test which verifies that $merge accounts for the size of let parameters and runtime constants
+ * when it serializes writes to send to other nodes.
+ *
+ * @tags: [
+ * # The $merge in this test targets the '_id' field, and requires a unique index.
+ * expects_explicit_underscore_id_index,
+ * ]
+ */
+(function() {
+"use strict";
+
+load('jstests/libs/fixture_helpers.js'); // For isReplSet().
+
+// Function to run the test against a test fixture. Accepts an object that contains the following
+// fields:
+// - testFixture: The fixture to run the test against.
+// - conn: Connection to the test fixture specified above.
+// - shardLocal and shardOutput: Indicates whether the local/output collection should be sharded in
+// this test run (ignored when not running against a sharded cluster).
+function runTest({testFixture, conn, shardLocal, shardOutput}) {
+ const dbName = "db";
+ const collName = "merge_let_params";
+ const dbCollName = dbName + "." + collName;
+ const outCollName = "outcoll";
+ const dbOutCollName = dbName + "." + outCollName;
+ const admin = conn.getDB("admin");
+ const isReplSet = FixtureHelpers.isReplSet(admin);
+
+ function shardColls() {
+ // When running against a sharded cluster, configure the collections according to
+ // 'shardLocal' and 'shardOutput'.
+ if (!isReplSet) {
+ assert.commandWorked(admin.runCommand({enableSharding: dbName}));
+ testFixture.ensurePrimaryShard(dbName, testFixture.shard0.shardName);
+ if (shardLocal) {
+ testFixture.shardColl(collName, {_id: 1}, {_id: 0}, {_id: 0}, dbName);
+ }
+ if (shardOutput) {
+ testFixture.shardColl(outCollName, {_id: 1}, {_id: 0}, {_id: 0}, dbName);
+ }
+ }
+ }
+ const coll = conn.getCollection(dbCollName);
+ const outColl = conn.getCollection(dbOutCollName);
+ coll.drop();
+ outColl.drop();
+ shardColls();
+
+ // Insert two large documents in both collections. By inserting the documents with the same _id
+ // values in both collections and splitting these documents between chunks, this will guarantee
+ // that we need to serialize and send update command(s) across the wire when targeting the
+ // output collection.
+ const kOneMB = 1024 * 1024;
+ const kDataString = "a".repeat(4 * kOneMB);
+ const kDocs = [{_id: 2, data: kDataString}, {_id: -2, data: kDataString}];
+ assert.commandWorked(coll.insertMany(kDocs));
+ assert.commandWorked(outColl.insertMany(kDocs));
+
+ // The sizes of the different update command components are deliberately chosen to test the
+ // batching logic when the update is targeted to another node in the cluster. In particular, the
+ // update command will contain the 10MB 'outFieldValue' and we will be updating two 4MB
+ // documents. The 18MB total exceeds the 16MB size limit, so we expect the batching logic to
+ // split the two documents into separate batches of 14MB each.
+ const outFieldValue = "a".repeat(10 * kOneMB);
+ let aggCommand = {
+ pipeline: [{
+ $merge: {
+ into: {db: "db", coll: outCollName},
+ on: "_id",
+ whenMatched: [{$addFields: {out: "$$outField"}}],
+ whenNotMatched: "insert"
+ }
+ }],
+ cursor: {},
+ let : {"outField": outFieldValue}
+ };
+
+ // If this is a replica set, we need to target a secondary node to force writes to go over
+ // the wire.
+ const aggColl = isReplSet ? testFixture.getSecondary().getCollection(dbCollName) : coll;
+
+ if (isReplSet) {
+ aggCommand["$readPreference"] = {mode: "secondary"};
+ }
+
+ // The aggregate should not fail.
+ assert.commandWorked(aggColl.runCommand("aggregate", aggCommand));
+
+ // Verify that each document in the output collection contains the value of 'outField'.
+ let outContents = outColl.find().toArray();
+ for (const res of outContents) {
+ const out = res["out"];
+ assert.eq(out, outFieldValue, outContents);
+ }
+
+ assert(coll.drop());
+ assert(outColl.drop());
+ shardColls();
+
+ // Insert four large documents in both collections. As before, this will force updates to be
+ // sent across the wire, but this will generate double the batches.
+ const kMoreDocs = [
+ {_id: -2, data: kDataString},
+ {_id: -1, data: kDataString},
+ {_id: 1, data: kDataString},
+ {_id: 2, data: kDataString},
+ ];
+
+ assert.commandWorked(coll.insertMany(kMoreDocs));
+ assert.commandWorked(outColl.insertMany(kMoreDocs));
+
+ // The aggregate should not fail.
+ assert.commandWorked(aggColl.runCommand("aggregate", aggCommand));
+
+ // Verify that each document in the output collection contains the value of 'outField'.
+ outContents = outColl.find().toArray();
+ for (const res of outContents) {
+ const out = res["out"];
+ assert.eq(out, outFieldValue, outContents);
+ }
+
+ assert(coll.drop());
+ assert(outColl.drop());
+ shardColls();
+
+ // If the documents and the let parameters are large enough, the $merge is expected to fail.
+ const kVeryLargeDataString = "a".repeat(10 * kOneMB);
+ const kLargeDocs =
+ [{_id: 2, data: kVeryLargeDataString}, {_id: -2, data: kVeryLargeDataString}];
+ assert.commandWorked(coll.insertMany(kLargeDocs));
+ assert.commandWorked(outColl.insertMany(kLargeDocs));
+ assert.commandFailedWithCode(aggColl.runCommand("aggregate", aggCommand),
+ ErrorCodes.BSONObjectTooLarge);
+}
+
+// Test against a replica set.
+const rst = new ReplSetTest({nodes: 2});
+rst.startSet();
+rst.initiate();
+rst.awaitSecondaryNodes();
+
+runTest({testFixture: rst, conn: rst.getPrimary()});
+
+rst.stopSet();
+
+// Test against a sharded cluster.
+const st = new ShardingTest({shards: 2, mongos: 1});
+runTest({testFixture: st, conn: st.s0, shardLocal: false, shardOutput: false});
+runTest({testFixture: st, conn: st.s0, shardLocal: true, shardOutput: false});
+runTest({testFixture: st, conn: st.s0, shardLocal: false, shardOutput: true});
+runTest({testFixture: st, conn: st.s0, shardLocal: true, shardOutput: true});
+
+st.stop();
+})();
diff --git a/jstests/sharding/merge_with_drop_shard.js b/jstests/sharding/merge_with_drop_shard.js
index bc50b8fc058..bc0b18715ce 100644
--- a/jstests/sharding/merge_with_drop_shard.js
+++ b/jstests/sharding/merge_with_drop_shard.js
@@ -29,7 +29,6 @@ function setAggHang(mode) {
function removeShard(shard) {
// We need the balancer to drain all the chunks out of the shard that is being removed.
assert.commandWorked(st.startBalancer());
- st.waitForBalancer(true, 60000);
var res = st.s.adminCommand({removeShard: shard.shardName});
assert.commandWorked(res);
assert.eq('started', res.state);
@@ -45,7 +44,6 @@ function removeShard(shard) {
st.configRS.awaitLastOpCommitted();
assert.commandWorked(st.s.adminCommand({flushRouterConfig: 1}));
assert.commandWorked(st.stopBalancer());
- st.waitForBalancer(false, 60000);
}
function addShard(shard) {
diff --git a/jstests/sharding/migrateBig.js b/jstests/sharding/migrateBig.js
index 60306797dfd..aa237832dd3 100644
--- a/jstests/sharding/migrateBig.js
+++ b/jstests/sharding/migrateBig.js
@@ -1,7 +1,10 @@
(function() {
'use strict';
-var s = new ShardingTest({name: "migrateBig", shards: 2, other: {chunkSize: 1}});
+load("jstests/libs/feature_flag_util.js");
+
+var s = new ShardingTest(
+ {name: "migrateBig", shards: 2, other: {chunkSize: 1, enableAutoSplit: false}});
assert.commandWorked(
s.config.settings.update({_id: "balancer"}, {$set: {_waitForDelete: true}}, true));
@@ -58,11 +61,7 @@ s.printShardingStatus();
s.startBalancer();
-assert.soon(function() {
- var x = s.chunkDiff("foo", "test");
- print("chunk diff: " + x);
- return x < 2;
-}, "no balance happened", 8 * 60 * 1000, 2000);
+s.awaitBalance('foo', 'test', 60 * 1000);
s.stop();
})();
diff --git a/jstests/sharding/migrateBig_balancer.js b/jstests/sharding/migrateBig_balancer.js
deleted file mode 100644
index 2517a11a70c..00000000000
--- a/jstests/sharding/migrateBig_balancer.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * This test is labeled resource intensive because its total io_write is 95MB compared to a median
- * of 5MB across all sharding tests in wiredTiger.
- * @tags: [resource_intensive]
- */
-(function() {
-"use strict";
-
-load("jstests/sharding/libs/find_chunks_util.js");
-
-var st = new ShardingTest(
- {name: 'migrateBig_balancer', shards: 2, other: {enableBalancer: true, chunkSize: 64}});
-var mongos = st.s;
-var admin = mongos.getDB("admin");
-var db = mongos.getDB("test");
-var coll = db.getCollection("stuff");
-
-assert.commandWorked(admin.runCommand({enablesharding: coll.getDB().getName()}));
-st.ensurePrimaryShard(coll.getDB().getName(), st.shard1.shardName);
-
-var data = "x";
-var nsq = 16;
-var n = 255;
-
-for (var i = 0; i < nsq; i++)
- data += data;
-
-var dataObj = {};
-for (var i = 0; i < n; i++)
- dataObj["data-" + i] = data;
-
-var bulk = coll.initializeUnorderedBulkOp();
-for (var i = 0; i < 40; i++) {
- bulk.insert({data: dataObj});
-}
-
-assert.commandWorked(bulk.execute());
-assert.eq(40, coll.count(), "prep1");
-
-assert.commandWorked(admin.runCommand({shardcollection: "" + coll, key: {_id: 1}}));
-st.printShardingStatus();
-
-assert.lt(5,
- findChunksUtil.findChunksByNs(mongos.getDB("config"), "test.stuff").count(),
- "not enough chunks");
-
-assert.soon(() => {
- const aggMatch = (function() {
- const collMetadata = mongos.getDB("config").collections.findOne({_id: "test.stuff"});
- if (collMetadata.timestamp) {
- return {$match: {uuid: collMetadata.uuid}};
- } else {
- return {$match: {ns: "test.stuff"}};
- }
- }());
- let res = mongos.getDB("config")
- .chunks.aggregate([aggMatch, {$group: {_id: "$shard", nChunks: {$sum: 1}}}])
- .toArray();
- printjson(res);
- return res.length > 1 && Math.abs(res[0].nChunks - res[1].nChunks) <= 3;
-}, "never migrated", 10 * 60 * 1000, 1000);
-
-st.stop();
-})();
diff --git a/jstests/sharding/min_max_key.js b/jstests/sharding/min_max_key.js
new file mode 100644
index 00000000000..7235216580e
--- /dev/null
+++ b/jstests/sharding/min_max_key.js
@@ -0,0 +1,28 @@
+/*
+ * Test writing and targeting of Max/Min key values
+ */
+
+(function() {
+'use strict';
+
+const st = new ShardingTest({});
+const coll = st.s.getDB(jsTestName())['coll'];
+
+st.shardColl(coll, {x: 1});
+
+assert.commandWorked(coll.insert({x: MaxKey}));
+assert.eq(1, coll.countDocuments({}));
+assert.eq(1, coll.countDocuments({x: MaxKey}));
+assert.commandWorked(coll.remove({x: MaxKey}));
+assert.eq(0, coll.countDocuments({}));
+assert.eq(0, coll.countDocuments({x: MaxKey}));
+
+assert.commandWorked(coll.insert({x: MinKey}));
+assert.eq(1, coll.countDocuments({}));
+assert.eq(1, coll.countDocuments({x: MinKey}));
+assert.commandWorked(coll.remove({x: MinKey}));
+assert.eq(0, coll.countDocuments({}));
+assert.eq(0, coll.countDocuments({x: MinKey}));
+
+st.stop();
+})();
diff --git a/jstests/sharding/mongos_validate_writes.js b/jstests/sharding/mongos_validate_writes.js
index 0852dccf763..fc6d4dccab3 100644
--- a/jstests/sharding/mongos_validate_writes.js
+++ b/jstests/sharding/mongos_validate_writes.js
@@ -22,8 +22,8 @@ st.ensurePrimaryShard(coll.getDB().getName(), st.shard1.shardName);
coll.createIndex({a: 1});
// Shard the collection on {a: 1} and move one chunk to another shard. Updates need to be across
-// two shards to trigger an error, otherwise they are versioned and will succeed after raising
-// a StaleConfigException.
+// two shards to trigger an error, otherwise they are versioned and will succeed after raising a
+// StaleConfig error.
st.shardColl(coll, {a: 1}, {a: 0}, {a: 1}, coll.getDB(), true);
// Let the stale mongos see the collection state
diff --git a/jstests/sharding/move_chunk_allowMigrations.js b/jstests/sharding/move_chunk_allowMigrations.js
index 6395a7a7424..06dae4bc2c1 100644
--- a/jstests/sharding/move_chunk_allowMigrations.js
+++ b/jstests/sharding/move_chunk_allowMigrations.js
@@ -6,17 +6,19 @@
*
* @tags: [
* does_not_support_stepdowns,
+ * requires_fcv_60,
* ]
*/
(function() {
'use strict';
+load("jstests/libs/feature_flag_util.js");
load('jstests/libs/fail_point_util.js');
load('jstests/libs/parallel_shell_helpers.js');
load("jstests/sharding/libs/find_chunks_util.js");
load("jstests/sharding/libs/shard_versioning_util.js");
-const st = new ShardingTest({shards: 2});
+const st = new ShardingTest({shards: 2, other: {chunkSize: 1, enableAutoSplit: false}});
const configDB = st.s.getDB("config");
// Resets database dbName and enables sharding and establishes shard0 as primary, test case agnostic
@@ -145,12 +147,14 @@ function testAllowMigrationsFalseDisablesBalancer(allowMigrations, collBSetNoBal
assert.commandWorked(st.s.adminCommand({shardCollection: collA.getFullName(), key: {_id: 1}}));
assert.commandWorked(st.s.adminCommand({shardCollection: collB.getFullName(), key: {_id: 1}}));
+ const bigString = 'X'.repeat(1024 * 1024); // 1MB
+
// Split both collections into 4 chunks so balancing can occur.
for (let coll of [collA, collB]) {
- coll.insert({_id: 1});
- coll.insert({_id: 10});
- coll.insert({_id: 20});
- coll.insert({_id: 30});
+ coll.insert({_id: 1, s: bigString});
+ coll.insert({_id: 10, s: bigString});
+ coll.insert({_id: 20, s: bigString});
+ coll.insert({_id: 30, s: bigString});
assert.commandWorked(st.splitAt(coll.getFullName(), {_id: 10}));
assert.commandWorked(st.splitAt(coll.getFullName(), {_id: 20}));
@@ -177,20 +181,9 @@ function testAllowMigrationsFalseDisablesBalancer(allowMigrations, collBSetNoBal
}));
st.startBalancer();
- assert.soon(() => {
- st.awaitBalancerRound();
- const shard0Chunks =
- findChunksUtil
- .findChunksByNs(configDB, collA.getFullName(), {shard: st.shard0.shardName})
- .itcount();
- const shard1Chunks =
- findChunksUtil
- .findChunksByNs(configDB, collA.getFullName(), {shard: st.shard1.shardName})
- .itcount();
- jsTestLog(`shard0 chunks ${shard0Chunks}, shard1 chunks ${shard1Chunks}`);
- return shard0Chunks == 2 && shard1Chunks == 2;
- }, `Balancer failed to balance ${collA.getFullName()}`, 1000 * 60 * 10);
+ st.awaitBalance(collAName, dbName, 10 * 60000 /* 10min timeout */);
st.stopBalancer();
+ st.verifyCollectionIsBalanced(collA);
const collABalanceStatus =
assert.commandWorked(st.s.adminCommand({balancerCollectionStatus: collA.getFullName()}));
diff --git a/jstests/sharding/move_chunk_concurrent_cloning.js b/jstests/sharding/move_chunk_concurrent_cloning.js
new file mode 100644
index 00000000000..b812d5e72d1
--- /dev/null
+++ b/jstests/sharding/move_chunk_concurrent_cloning.js
@@ -0,0 +1,115 @@
+/**
+ * @tags: [
+ * featureFlagConcurrencyInChunkMigration,
+ * requires_fcv_60,
+ * ]
+ */
+(function() {
+"use strict";
+
+load('./jstests/libs/chunk_manipulation_util.js');
+
+const runParallelMoveChunk = (numThreads) => {
+ // For startParallelOps to write its state
+ let staticMongod = MongoRunner.runMongod({});
+
+ let st = new ShardingTest({shards: 2});
+ st.stopBalancer();
+
+ const kThreadCount = numThreads;
+ const kPadding = new Array(1024).join("x");
+
+ let testDB = st.s.getDB('test');
+ assert.commandWorked(testDB.adminCommand({enableSharding: 'test'}));
+ st.ensurePrimaryShard('test', st.shard0.shardName);
+ assert.commandWorked(testDB.adminCommand({shardCollection: 'test.user', key: {x: 1}}));
+
+ let shardKeyVal = 0;
+ const kDocsInBatch = 8 * 1000;
+ const kMinCollSize = 128 * 1024 * 1024;
+ let approxInsertedSize = 0;
+ while (approxInsertedSize < kMinCollSize) {
+ var bulk = testDB.user.initializeUnorderedBulkOp();
+ for (let docs = 0; docs < kDocsInBatch; docs++) {
+ shardKeyVal++;
+ bulk.insert({_id: shardKeyVal, x: shardKeyVal, padding: kPadding});
+ }
+ assert.commandWorked(bulk.execute());
+
+ approxInsertedSize = approxInsertedSize + (kDocsInBatch * 1024);
+ }
+
+ const kInitialLoadFinalKey = shardKeyVal;
+
+ print(`Running tests with chunkMigrationConcurrency == ${kThreadCount}`);
+ st._rs.forEach((replSet) => {
+ assert.commandWorked(replSet.test.getPrimary().adminCommand(
+ {setParameter: 1, chunkMigrationConcurrency: kThreadCount}));
+ });
+
+ const configCollEntry =
+ st.s.getDB('config').getCollection('collections').findOne({_id: 'test.user'});
+ let chunks = st.s.getDB('config').chunks.find({uuid: configCollEntry.uuid}).toArray();
+ assert.eq(1, chunks.length, tojson(chunks));
+
+ let joinMoveChunk =
+ moveChunkParallel(staticMongod, st.s0.host, {x: 0}, null, 'test.user', st.shard1.shardName);
+
+ // Migration cloning scans by shard key order. Perform some writes against the collection on
+ // both the lower and upper ends of the shard key values while migration is happening to
+ // exercise xferMods logic.
+ const kDeleteIndexOffset = kInitialLoadFinalKey - 3000;
+ const kUpdateIndexOffset = kInitialLoadFinalKey - 5000;
+ for (let x = 0; x < 1000; x++) {
+ assert.commandWorked(testDB.user.remove({x: x}));
+ assert.commandWorked(testDB.user.update({x: 4000 + x}, {$set: {updated: true}}));
+
+ assert.commandWorked(testDB.user.remove({x: kDeleteIndexOffset + x}));
+ assert.commandWorked(
+ testDB.user.update({x: kUpdateIndexOffset + x}, {$set: {updated: true}}));
+
+ let newShardKey = kInitialLoadFinalKey + x + 1;
+ assert.commandWorked(testDB.user.insert({_id: newShardKey, x: newShardKey}));
+ }
+
+ joinMoveChunk();
+
+ let shardKeyIdx = 1000; // Index starts at 1k since we deleted the first 1k docs.
+ let cursor = testDB.user.find().sort({x: 1});
+
+ while (cursor.hasNext()) {
+ let next = cursor.next();
+ assert.eq(next.x, shardKeyIdx);
+
+ if ((shardKeyIdx >= 4000 && shardKeyIdx < 5000) ||
+ (shardKeyIdx >= kUpdateIndexOffset && shardKeyIdx < (kUpdateIndexOffset + 1000))) {
+ assert.eq(true, next.updated, tojson(next));
+ }
+
+ shardKeyIdx++;
+
+ if (shardKeyIdx == kDeleteIndexOffset) {
+ shardKeyIdx += 1000;
+ }
+ }
+
+ shardKeyIdx--;
+ assert.eq(shardKeyIdx, kInitialLoadFinalKey + 1000);
+
+ // server Status on the receiving shard
+ var serverStatus = st.shard1.getDB('admin').runCommand({serverStatus: 1});
+
+ assert.eq(kThreadCount,
+ serverStatus.shardingStatistics.chunkMigrationConcurrency,
+ tojson(serverStatus));
+ st.stop();
+ MongoRunner.stopMongod(staticMongod);
+};
+
+runParallelMoveChunk(1);
+
+// Run test a few times with random concurrency levels.
+for (let i = 1; i <= 4; i++) {
+ runParallelMoveChunk(Math.floor(Math.random() * 31) + 1);
+}
+})();
diff --git a/jstests/sharding/move_chunk_deferred_lookup.js b/jstests/sharding/move_chunk_deferred_lookup.js
new file mode 100644
index 00000000000..7e3a9149c94
--- /dev/null
+++ b/jstests/sharding/move_chunk_deferred_lookup.js
@@ -0,0 +1,101 @@
+/**
+ * Ensure that updates are not lost if they are made between processing deferred updates and reading
+ * from the updates list in _transferMods.
+ *
+ * @tags: [uses_transactions, uses_prepare_transaction, requires_persistence]
+ */
+
+(function() {
+"use strict";
+load('jstests/libs/chunk_manipulation_util.js');
+load("jstests/libs/fail_point_util.js");
+load('jstests/replsets/rslib.js');
+load('jstests/sharding/libs/create_sharded_collection_util.js');
+
+const dbName = "test";
+const collName = "user";
+const staticMongod = MongoRunner.runMongod({});
+const st = new ShardingTest({shards: {rs0: {nodes: 2}, rs1: {nodes: 1}}});
+const collection = st.s.getDB(dbName).getCollection(collName);
+const lsid = {
+ id: UUID()
+};
+const txnNumber = 0;
+
+function setup() {
+ CreateShardedCollectionUtil.shardCollectionWithChunks(collection, {_id: 1}, [
+ {min: {_id: MinKey}, max: {_id: 10}, shard: st.shard0.shardName},
+ {min: {_id: 10}, max: {_id: MaxKey}, shard: st.shard1.shardName},
+ ]);
+
+ for (let i = 0; i < 20; i++) {
+ assert.commandWorked(collection.insertOne({_id: i, x: i}));
+ }
+}
+
+function prepareTransactionAndTriggerFailover() {
+ assert.commandWorked(st.s.getDB(dbName).runCommand({
+ update: collName,
+ updates: [
+ {q: {_id: 1}, u: {$set: {x: 5}}},
+ {q: {_id: 2}, u: {$set: {x: -10}}},
+ ],
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ stmtId: NumberInt(0),
+ startTransaction: true,
+ autocommit: false,
+ }));
+
+ const result = assert.commandWorked(st.shard0.getDB(dbName).adminCommand({
+ prepareTransaction: 1,
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false,
+ writeConcern: {w: "majority"},
+ }));
+
+ let oldSecondary = st.rs0.getSecondary();
+
+ st.rs0.stepUp(oldSecondary);
+
+ awaitRSClientHosts(st.s, oldSecondary, {ok: true, ismaster: true});
+
+ return result.prepareTimestamp;
+}
+
+function commitPreparedTransaction(prepareTimestamp) {
+ assert.commandWorked(
+ st.shard0.getDB(dbName).adminCommand(Object.assign({
+ commitTransaction: 1,
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false,
+ },
+ {commitTimestamp: prepareTimestamp})));
+}
+
+function runMoveChunkAndCommitTransaction() {
+ const joinMoveChunk = moveChunkParallel(
+ staticMongod, st.s.host, {_id: 1}, null, 'test.user', st.shard1.shardName);
+ pauseMigrateAtStep(st.shard1, migrateStepNames.catchup);
+ waitForMoveChunkStep(st.shard0, moveChunkStepNames.startedMoveChunk);
+ commitPreparedTransaction(prepareTimestamp);
+ unpauseMigrateAtStep(st.shard1, migrateStepNames.catchup);
+ return joinMoveChunk;
+}
+
+setup();
+const prepareTimestamp = prepareTransactionAndTriggerFailover();
+const fp = configureFailPoint(st.rs0.getPrimary(), "hangAfterProcessingDeferredXferMods");
+const joinMoveChunk = runMoveChunkAndCommitTransaction();
+fp.wait();
+assert.commandWorked(st.s.getDB(dbName).getCollection(collName).update({_id: 4}, {$set: {x: 501}}));
+fp.off();
+joinMoveChunk();
+assert.eq(collection.findOne({_id: 4}).x, 501);
+
+st.stop();
+
+MongoRunner.stopMongod(staticMongod);
+})();
diff --git a/jstests/sharding/move_chunk_interrupt_postimage.js b/jstests/sharding/move_chunk_interrupt_postimage.js
new file mode 100644
index 00000000000..69537bb9ce0
--- /dev/null
+++ b/jstests/sharding/move_chunk_interrupt_postimage.js
@@ -0,0 +1,42 @@
+/**
+ * Tests that chunk migration interruption before processing post/pre image oplog entry leads
+ * to consistent config.transactions data across primary and secondaries (SERVER-67492)
+ */
+
+(function() {
+"use strict";
+
+load("jstests/sharding/libs/create_sharded_collection_util.js");
+load("jstests/libs/fail_point_util.js");
+load('jstests/libs/parallel_shell_helpers.js');
+
+const st = new ShardingTest({mongos: 1, config: 1, shards: 2, rs: {nodes: 2}});
+const interruptBeforeProcessingPrePostImageOriginatingOpFP =
+ configureFailPoint(st.rs1.getPrimary(), "interruptBeforeProcessingPrePostImageOriginatingOp");
+
+const collection = st.s.getCollection("test.mycoll");
+CreateShardedCollectionUtil.shardCollectionWithChunks(collection, {x: 1}, [
+ {min: {x: MinKey}, max: {x: 0}, shard: st.shard0.shardName},
+ {min: {x: 0}, max: {x: 100}, shard: st.shard0.shardName},
+ {min: {x: 100}, max: {x: MaxKey}, shard: st.shard1.shardName},
+]);
+
+assert.commandWorked(collection.insert({_id: 0, x: 10}));
+assert.commandWorked(collection.runCommand("findAndModify", {
+ query: {x: 10},
+ update: {$set: {y: 2}},
+ new: true,
+ txnNumber: NumberLong(1),
+}));
+
+const res = st.s.adminCommand({
+ moveChunk: collection.getFullName(),
+ find: {x: 10},
+ to: st.shard1.shardName,
+});
+assert.commandFailedWithCode(res, ErrorCodes.CommandFailed);
+interruptBeforeProcessingPrePostImageOriginatingOpFP.wait();
+
+interruptBeforeProcessingPrePostImageOriginatingOpFP.off();
+st.stop();
+})();
diff --git a/jstests/sharding/move_chunk_permitMigrations.js b/jstests/sharding/move_chunk_permitMigrations.js
index e984a9fbdfb..b2a2f545f25 100644
--- a/jstests/sharding/move_chunk_permitMigrations.js
+++ b/jstests/sharding/move_chunk_permitMigrations.js
@@ -4,7 +4,7 @@
*
* @tags: [
* does_not_support_stepdowns,
- * requires_fcv_52,
+ * requires_fcv_60,
* ]
*/
(function() {
@@ -15,7 +15,7 @@ load('jstests/libs/parallel_shell_helpers.js');
load("jstests/sharding/libs/find_chunks_util.js");
load("jstests/sharding/libs/shard_versioning_util.js");
-const st = new ShardingTest({shards: 2});
+const st = new ShardingTest({shards: 2, other: {chunkSize: 1, enableAutoSplit: false}});
const configDB = st.s.getDB("config");
const dbName = 'AllowMigrations';
@@ -65,12 +65,14 @@ const testBalancer = function(setAllowMigrations, collBSetNoBalanceParam) {
assert.commandWorked(st.s.adminCommand({shardCollection: collA.getFullName(), key: {_id: 1}}));
assert.commandWorked(st.s.adminCommand({shardCollection: collB.getFullName(), key: {_id: 1}}));
+ const bigString = 'X'.repeat(1024 * 1024); // 1MB
+
// Split both collections into 4 chunks so balancing can occur.
for (let coll of [collA, collB]) {
- coll.insert({_id: 1});
- coll.insert({_id: 10});
- coll.insert({_id: 20});
- coll.insert({_id: 30});
+ coll.insert({_id: 1, s: bigString});
+ coll.insert({_id: 10, s: bigString});
+ coll.insert({_id: 20, s: bigString});
+ coll.insert({_id: 30, s: bigString});
assert.commandWorked(st.splitAt(coll.getFullName(), {_id: 10}));
assert.commandWorked(st.splitAt(coll.getFullName(), {_id: 20}));
@@ -96,20 +98,9 @@ const testBalancer = function(setAllowMigrations, collBSetNoBalanceParam) {
setAllowMigrationsCmd(collB.getFullName(), setAllowMigrations);
st.startBalancer();
- assert.soon(() => {
- st.awaitBalancerRound();
- const shard0Chunks =
- findChunksUtil
- .findChunksByNs(configDB, collA.getFullName(), {shard: st.shard0.shardName})
- .itcount();
- const shard1Chunks =
- findChunksUtil
- .findChunksByNs(configDB, collA.getFullName(), {shard: st.shard1.shardName})
- .itcount();
- jsTestLog(`shard0 chunks ${shard0Chunks}, shard1 chunks ${shard1Chunks}`);
- return shard0Chunks == 2 && shard1Chunks == 2;
- }, `Balancer failed to balance ${collA.getFullName()}`, 1000 * 60 * 10);
+ st.awaitBalance(collAName, dbName);
st.stopBalancer();
+ st.verifyCollectionIsBalanced(collA);
const collABalanceStatus =
assert.commandWorked(st.s.adminCommand({balancerCollectionStatus: collA.getFullName()}));
diff --git a/jstests/sharding/move_primary_clone_test.js b/jstests/sharding/move_primary_clone_test.js
index 404562e7cd1..5ce928c9580 100644
--- a/jstests/sharding/move_primary_clone_test.js
+++ b/jstests/sharding/move_primary_clone_test.js
@@ -58,7 +58,12 @@ function checkCollectionsCopiedCorrectly(fromShard, toShard, sharded, barUUID, f
var indexes = res.cursor.firstBatch;
indexes.sort(sortByName);
- assert.eq(indexes.length, 2);
+ // TODO SERVER-74252: once 7.0 becomes LastLTS we can assume that the movePrimary will never
+ // copy indexes of sharded collections.
+ if (sharded)
+ assert(indexes.length == 1 || indexes.length == 2);
+ else
+ assert(indexes.length == 2);
indexes.forEach((index, i) => {
var expected;
diff --git a/jstests/sharding/move_range_basic.js b/jstests/sharding/move_range_basic.js
index 25cd0da3497..401516bbf73 100644
--- a/jstests/sharding/move_range_basic.js
+++ b/jstests/sharding/move_range_basic.js
@@ -2,7 +2,6 @@
* Basic tests for moveRange.
*
* @tags: [
- * featureFlagNoMoreAutoSplitter,
* requires_fcv_60,
* ]
*/
diff --git a/jstests/sharding/mr_single_reduce_split.js b/jstests/sharding/mr_single_reduce_split.js
new file mode 100644
index 00000000000..1a6d20f0e1f
--- /dev/null
+++ b/jstests/sharding/mr_single_reduce_split.js
@@ -0,0 +1,55 @@
+/**
+ * This jstest verifies that the mrEnableSingleReduceOptimization flag works properly in a sharded
+ * cluster when there are documents on multiple chunks that need to be merged.
+ * @tags: [
+ * backport_required_multiversion,
+ * ]
+ */
+(function() {
+const st = new ShardingTest({
+ shards: 2,
+ mongos: 1,
+ other: {
+ mongosOptions: {setParameter: {mrEnableSingleReduceOptimization: true}},
+ shardOptions: {setParameter: {mrEnableSingleReduceOptimization: true}},
+ }
+});
+
+const mongosDB = st.s0.getDB(jsTestName());
+const mongosColl = mongosDB[jsTestName()];
+
+assert.commandWorked(mongosDB.dropDatabase());
+assert.commandWorked(mongosDB.adminCommand({enableSharding: mongosDB.getName()}));
+st.ensurePrimaryShard(mongosDB.getName(), st.shard0.shardName);
+
+assert.commandWorked(
+ mongosDB.adminCommand({shardCollection: mongosColl.getFullName(), key: {_id: 1}}));
+
+// Split the collection into 2 chunks: [minKey, 0) and [0, maxKey].
+assert.commandWorked(mongosDB.adminCommand({split: mongosColl.getFullName(), middle: {_id: 0}}));
+
+// Move the [0, MaxKey) chunk to the second shard.
+assert.commandWorked(mongosDB.adminCommand(
+ {moveChunk: mongosColl.getFullName(), find: {_id: 50}, to: st.shard1.shardName}));
+
+assert.commandWorked(mongosColl.insert({_id: -1}));
+const map = function() {
+ emit(0, {val: "mapped value"});
+};
+
+const reduce = function(key, values) {
+ return {val: "reduced value"};
+};
+
+let res = assert.commandWorked(mongosDB.runCommand(
+ {mapReduce: mongosColl.getName(), map: map, reduce: reduce, out: {inline: 1}}));
+assert.eq(res.results[0], {_id: 0, value: {val: "mapped value"}});
+
+assert.commandWorked(mongosColl.insert({_id: 1}));
+
+res = assert.commandWorked(mongosDB.runCommand(
+ {mapReduce: mongosColl.getName(), map: map, reduce: reduce, out: {inline: 1}}));
+assert.eq(res.results[0], {_id: 0, value: {val: "reduced value"}});
+
+st.stop();
+}());
diff --git a/jstests/sharding/multi_writes_with_shard_version_ignored_dont_bubble_up_critical_section.js b/jstests/sharding/multi_writes_with_shard_version_ignored_dont_bubble_up_critical_section.js
new file mode 100644
index 00000000000..82155211bef
--- /dev/null
+++ b/jstests/sharding/multi_writes_with_shard_version_ignored_dont_bubble_up_critical_section.js
@@ -0,0 +1,150 @@
+/*
+ * Tests that multi-writes where the router attaches 'shardVersion: IGNORED' (i.e. if they need to
+ * target several shards AND are not part of a txn) do not bubble up StaleConfig errors due to
+ * ongoing critical sections. Instead, the shard yields and waits for the critical section to finish
+ * and then continues the write plan.
+ */
+
+(function() {
+"use strict";
+
+load('jstests/libs/parallel_shell_helpers.js');
+load("jstests/libs/fail_point_util.js");
+
+// Configure 'internalQueryExecYieldIterations' on both shards such that operations will yield on
+// each 10th PlanExecuter iteration.
+var st = new ShardingTest({
+ shards: 2,
+ rs: {setParameter: {internalQueryExecYieldIterations: 10}},
+ other: {enableBalancer: false}
+});
+
+const dbName = "test";
+const collName = "foo";
+const ns = dbName + "." + collName;
+const numDocs = 100;
+let coll = st.s.getCollection(ns);
+
+assert.commandWorked(
+ st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
+
+function setupTest() {
+ coll.drop();
+ assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {x: 1}}));
+
+ // Create three chunks:
+ // - [MinKey, 0) initially on shard0 and has no documents. This chunk will be migrated during
+ // the test execution.
+ // - [0, numDocs) on shard 0. Contains 'numDocs' documents.
+ // - [numDocs, MaxKey) shard 1. Contains no documents.
+ assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: 0}}));
+ assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: numDocs}}));
+ assert.commandWorked(st.s.adminCommand(
+ {moveChunk: ns, find: {x: numDocs}, to: st.shard1.shardName, waitForDelete: true}));
+
+ jsTest.log("Inserting initial data.");
+ const bulkOp = coll.initializeOrderedBulkOp();
+ for (let i = 0; i < numDocs; ++i) {
+ bulkOp.insert({x: i, c: 0});
+ }
+ assert.commandWorked(bulkOp.execute());
+ jsTest.log("Inserted initial data.");
+}
+
+function runMigration() {
+ const awaitResult = startParallelShell(
+ funWithArgs(function(ns, toShard) {
+ jsTest.log("Starting migration.");
+ assert.commandWorked(db.adminCommand({moveChunk: ns, find: {x: -1}, to: toShard}));
+ jsTest.log("Completed migration.");
+ }, ns, st.shard1.shardName), st.s.port);
+
+ return awaitResult;
+}
+
+function updateOperationFn(shardColl, numInitialDocsOnShard0) {
+ load('jstests/sharding/libs/shard_versioning_util.js'); // For kIgnoredShardVersion
+
+ jsTest.log("Begin multi-update.");
+
+ // Send a multi-update with 'shardVersion: IGNORED' directly to the shard, as if we were a
+ // router.
+ const result = assert.commandWorked(shardColl.runCommand({
+ update: shardColl.getName(),
+ updates: [{q: {}, u: {$inc: {c: 1}}, multi: true}],
+ shardVersion: ShardVersioningUtil.kIgnoredShardVersion
+ }));
+
+ jsTest.log("End multi-update. Result: " + tojson(result));
+
+ // Check that all documents got updates. Despite the weak guarantees of {multi: true} writes
+ // concurrent with migrations, this has to be the case in this test because the migrated chunk
+ // does not contain any document.
+ assert.eq(numInitialDocsOnShard0, shardColl.find({c: 1}).itcount());
+}
+
+function deleteOperationFn(shardColl, numInitialDocsOnShard0) {
+ load('jstests/sharding/libs/shard_versioning_util.js'); // For kIgnoredShardVersion
+
+ jsTest.log("Begin multi-delete");
+
+ // Send a multi-delete with 'shardVersion: IGNORED' directly to the shard, as if we were a
+ // router.
+ const result = assert.commandWorked(shardColl.runCommand({
+ delete: shardColl.getName(),
+ deletes: [{q: {}, limit: 0}],
+ shardVersion: ShardVersioningUtil.kIgnoredShardVersion
+ }));
+
+ jsTest.log("End multi-delete. Result: " + tojson(result));
+
+ // Check that all documents got deleted. Despite the weak guarantees of {multi: true} writes
+ // concurrent with migrations, this has to be the case in this test because the migrated chunk
+ // does not contain any document.
+ assert.eq(0, shardColl.find().itcount());
+}
+
+function runTest(writeOpFn) {
+ setupTest();
+
+ let fp1 = configureFailPoint(
+ st.rs0.getPrimary(), 'setYieldAllLocksHang', {namespace: coll.getFullName()});
+
+ const awaitWriteResult = startParallelShell(
+ funWithArgs(function(writeOpFn, dbName, collName, numDocs) {
+ const shardColl = db.getSiblingDB(dbName)[collName];
+ writeOpFn(shardColl, numDocs);
+ }, writeOpFn, coll.getDB().getName(), coll.getName(), numDocs), st.rs0.getPrimary().port);
+
+ // Wait for the write op to yield.
+ fp1.wait();
+ jsTest.log("Multi-write yielded");
+
+ // Start chunk migration and wait for it to enter the critical section.
+ let failpointHangMigrationWhileInCriticalSection =
+ configureFailPoint(st.rs0.getPrimary(), 'moveChunkHangAtStep5');
+ const awaitMigration = runMigration();
+ failpointHangMigrationWhileInCriticalSection.wait();
+
+ // Let the multi-write resume from the yield.
+ jsTest.log("Resuming yielded multi-write");
+ fp1.off();
+
+ // Let the multi-write run for a bit after the resuming from yield. It will encounter the
+ // critical section.
+ sleep(1000);
+
+ // Let the migration continue and release the critical section.
+ jsTest.log("Letting migration exit its critical section and complete");
+ failpointHangMigrationWhileInCriticalSection.off();
+ awaitMigration();
+
+ // Wait for the write op to finish. It should succeed.
+ awaitWriteResult();
+}
+
+runTest(updateOperationFn);
+runTest(deleteOperationFn);
+
+st.stop();
+})();
diff --git a/jstests/sharding/prefix_shard_key.js b/jstests/sharding/prefix_shard_key.js
index 20b8224b790..78486319784 100644
--- a/jstests/sharding/prefix_shard_key.js
+++ b/jstests/sharding/prefix_shard_key.js
@@ -61,6 +61,7 @@ assert.commandWorked(s.s0.adminCommand({shardCollection: coll.getFullName(), key
assert.eq(2, coll.getIndexes().length);
// make sure balancing happens
+s.startBalancer();
s.awaitBalance(coll.getName(), db.getName());
// Make sure our initial balance cleanup doesn't interfere with later migrations.
diff --git a/jstests/sharding/prepare_transaction_then_migrate.js b/jstests/sharding/prepare_transaction_then_migrate.js
index 034259d02be..4411b3014c1 100644
--- a/jstests/sharding/prepare_transaction_then_migrate.js
+++ b/jstests/sharding/prepare_transaction_then_migrate.js
@@ -3,12 +3,14 @@
* 1. Ignore multi-statement transaction prepare conflicts in the clone phase, and
* 2. Pick up the changes for prepared transactions in the transfer mods phase.
*
- * @tags: [uses_transactions, uses_prepare_transaction]
+ * @tags: [uses_transactions, uses_prepare_transaction, requires_persistence]
*/
(function() {
"use strict";
load('jstests/libs/chunk_manipulation_util.js');
+load('jstests/replsets/rslib.js');
+load('jstests/sharding/libs/create_sharded_collection_util.js');
load('jstests/sharding/libs/sharded_transactions_helpers.js');
const dbName = "test";
@@ -16,58 +18,179 @@ const collName = "user";
const staticMongod = MongoRunner.runMongod({}); // For startParallelOps.
-const st = new ShardingTest({shards: {rs0: {nodes: 1}, rs1: {nodes: 1}}});
-st.adminCommand({enableSharding: 'test'});
-st.ensurePrimaryShard('test', st.shard0.shardName);
-st.adminCommand({shardCollection: 'test.user', key: {_id: 1}});
+const TestMode = {
+ kBasic: 'basic',
+ kWithStepUp: 'with stepUp',
+ kWithRestart: 'with restart',
+};
+
+let runTest = function(testMode) {
+ jsTest.log(`Running test in mode ${testMode}`);
+
+ const st = new ShardingTest(
+ {shards: {rs0: {nodes: testMode == TestMode.kWithStepUp ? 2 : 1}, rs1: {nodes: 1}}});
+ const collection = st.s.getDB(dbName).getCollection(collName);
+
+ CreateShardedCollectionUtil.shardCollectionWithChunks(collection, {x: 1}, [
+ {min: {x: MinKey}, max: {x: 0}, shard: st.shard0.shardName},
+ {min: {x: 0}, max: {x: 1000}, shard: st.shard0.shardName},
+ {min: {x: 1000}, max: {x: MaxKey}, shard: st.shard1.shardName},
+ ]);
+
+ assert.commandWorked(collection.insert([
+ {_id: 1, x: -1, note: "move into chunk range being migrated"},
+ {_id: 2, x: -2, note: "keep out of chunk range being migrated"},
+ {_id: 3, x: 50, note: "move out of chunk range being migrated"},
+ {_id: 4, x: 100, note: "keep in chunk range being migrated"},
+ ]));
+
+ const lsid = {id: UUID()};
+ const txnNumber = 0;
+ let stmtId = 0;
+
+ assert.commandWorked(st.s0.getDB(dbName).runCommand({
+ insert: collName,
+ documents: [
+ {_id: 5, x: -1.01, note: "move into chunk range being migrated"},
+ {_id: 6, x: -2.01, note: "keep out of chunk range being migrated"},
+ {_id: 7, x: 50.01, note: "move out of chunk range being migrated"},
+ {_id: 8, x: 100.01, note: "keep in chunk range being migrated"},
+ ],
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ stmtId: NumberInt(stmtId++),
+ startTransaction: true,
+ autocommit: false,
+ }));
+
+ assert.commandWorked(st.s.getDB(dbName).runCommand({
+ update: collName,
+ updates: [
+ {q: {x: -1}, u: {$set: {x: 5}}},
+ {q: {x: -2}, u: {$set: {x: -10}}},
+ {q: {x: 50}, u: {$set: {x: -20}}},
+ {q: {x: 100}, u: {$set: {x: 500}}},
+ {q: {x: -1.01}, u: {$set: {x: 5.01}}},
+ {q: {x: -2.01}, u: {$set: {x: -10.01}}},
+ {q: {x: 50.01}, u: {$set: {x: -20.01}}},
+ {q: {x: 100.01}, u: {$set: {x: 500.01}}},
+ ],
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ stmtId: NumberInt(stmtId++),
+ autocommit: false,
+ }));
+
+ const res = assert.commandWorked(st.shard0.getDB(dbName).adminCommand({
+ prepareTransaction: 1,
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false,
+ writeConcern: {w: "majority"},
+ }));
+
+ let prepareTimestamp = res.prepareTimestamp;
+
+ if (testMode == TestMode.kWithStepUp) {
+ st.rs0.stepUp(st.rs0.getSecondary());
+
+ // Wait for the config server to see the new primary.
+ // TODO SERVER-74177 Remove this once retry on NotWritablePrimary is implemented.
+ st.forEachConfigServer((conn) => {
+ awaitRSClientHosts(conn, st.rs0.getPrimary(), {ok: true, ismaster: true});
+ });
+ } else if (testMode == TestMode.kWithRestart) {
+ TestData.skipCollectionAndIndexValidation = true;
+ st.rs0.restart(st.rs0.getPrimary());
+ st.rs0.waitForPrimary();
+ TestData.skipCollectionAndIndexValidation = false;
+
+ assert.soon(() => {
+ try {
+ st.shard0.getDB(dbName).getCollection(collName).findOne();
+ return true;
+ } catch (ex) {
+ print("Caught expected once exception due to restart: " + tojson(ex));
+ return false;
+ }
+ });
+ }
-const session = st.s.startSession({causalConsistency: false});
-const sessionDB = session.getDatabase(dbName);
-const sessionColl = sessionDB.getCollection(collName);
+ const joinMoveChunk =
+ moveChunkParallel(staticMongod, st.s.host, {x: 1}, null, 'test.user', st.shard1.shardName);
-assert.commandWorked(sessionColl.insert({_id: 1}));
+ pauseMigrateAtStep(st.shard1, migrateStepNames.catchup);
-const lsid = {
- id: UUID()
+ // The donor shard only ignores prepare conflicts while scanning over the shard key index. We
+ // wait for donor shard to have finished buffering the RecordIds into memory from scanning over
+ // the shard key index before committing the transaction. Notably, the donor shard doesn't
+ // ignore prepare conflicts when fetching the full contents of the documents during calls to
+ // _migrateClone.
+ //
+ // TODO: SERVER-71028 Remove comment after making changes.
+
+ waitForMoveChunkStep(st.shard0, moveChunkStepNames.startedMoveChunk);
+
+ assert.commandWorked(
+ st.shard0.getDB(dbName).adminCommand(Object.assign({
+ commitTransaction: 1,
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false,
+ },
+ {commitTimestamp: prepareTimestamp})));
+
+ unpauseMigrateAtStep(st.shard1, migrateStepNames.catchup);
+
+ joinMoveChunk();
+
+ class ArrayCursor {
+ constructor(arr) {
+ this.i = 0;
+ this.arr = arr;
+ }
+
+ hasNext() {
+ return this.i < this.arr.length;
+ }
+
+ next() {
+ return this.arr[this.i++];
+ }
+ }
+
+ const expected = new ArrayCursor([
+ {_id: 1, x: 5, note: "move into chunk range being migrated"},
+ {_id: 2, x: -10, note: "keep out of chunk range being migrated"},
+ {_id: 3, x: -20, note: "move out of chunk range being migrated"},
+ {_id: 4, x: 500, note: "keep in chunk range being migrated"},
+ {_id: 5, x: 5.01, note: "move into chunk range being migrated"},
+ {_id: 6, x: -10.01, note: "keep out of chunk range being migrated"},
+ {_id: 7, x: -20.01, note: "move out of chunk range being migrated"},
+ {_id: 8, x: 500.01, note: "keep in chunk range being migrated"},
+ ]);
+
+ const diff = ((diff) => {
+ return {
+ docsWithDifferentContents: diff.docsWithDifferentContents.map(
+ ({first, second}) => ({expected: first, actual: second})),
+ docsExtraAfterMigration: diff.docsMissingOnFirst,
+ docsMissingAfterMigration: diff.docsMissingOnSecond,
+ };
+ })(DataConsistencyChecker.getDiff(expected, collection.find().sort({_id: 1, x: 1})));
+
+ assert.eq(diff, {
+ docsWithDifferentContents: [],
+ docsExtraAfterMigration: [],
+ docsMissingAfterMigration: [],
+ });
+
+ st.stop();
};
-const txnNumber = 0;
-const stmtId = 0;
-
-assert.commandWorked(st.s0.getDB(dbName).runCommand({
- insert: collName,
- documents: [{_id: 2}, {_id: 5}, {_id: 15}],
- lsid: lsid,
- txnNumber: NumberLong(txnNumber),
- stmtId: NumberInt(stmtId),
- startTransaction: true,
- autocommit: false,
-}));
-
-const res = assert.commandWorked(st.shard0.getDB(dbName).adminCommand({
- prepareTransaction: 1,
- lsid: lsid,
- txnNumber: NumberLong(txnNumber),
- autocommit: false,
-}));
-
-const joinMoveChunk =
- moveChunkParallel(staticMongod, st.s.host, {_id: 1}, null, 'test.user', st.shard1.shardName);
-
-// Wait for catchup to verify that the migration has exited the clone phase.
-waitForMigrateStep(st.shard1, migrateStepNames.catchup);
-
-assert.commandWorked(st.shard0.getDB(dbName).adminCommand({
- commitTransaction: 1,
- lsid: lsid,
- txnNumber: NumberLong(txnNumber),
- autocommit: false,
- commitTimestamp: res.prepareTimestamp,
-}));
-
-joinMoveChunk();
-
-assert.eq(sessionColl.find({_id: 2}).count(), 1);
-
-st.stop();
+
+runTest(TestMode.kBasic);
+runTest(TestMode.kWithStepUp);
+runTest(TestMode.kWithRestart);
+
MongoRunner.stopMongod(staticMongod);
})();
diff --git a/jstests/sharding/presplit.js b/jstests/sharding/presplit.js
deleted file mode 100644
index 32f30a6aba7..00000000000
--- a/jstests/sharding/presplit.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * @tags: [
- * requires_fcv_51,
- * ]
- */
-
-(function() {
-
-load("jstests/sharding/libs/find_chunks_util.js");
-
-var s = new ShardingTest({name: "presplit", shards: 2, mongos: 1, other: {chunkSize: 1}});
-
-s.adminCommand({enablesharding: "test"});
-s.ensurePrimaryShard('test', s.shard1.shardName);
-
-// Insert enough data in 'test.foo' to fill several chunks, if it was sharded.
-bigString = "";
-while (bigString.length < 10000) {
- bigString += "asdasdasdasdadasdasdasdasdasdasdasdasda";
-}
-
-db = s.getDB("test");
-inserted = 0;
-num = 0;
-var bulk = db.foo.initializeUnorderedBulkOp();
-while (inserted < (20 * 1024 * 1024)) {
- bulk.insert({_id: num++, s: bigString});
- inserted += bigString.length;
-}
-assert.commandWorked(bulk.execute());
-
-// Make sure that there's only one chunk holding all the data.
-s.printChunks();
-primary = s.getPrimaryShard("test").getDB("test");
-assert.eq(0, s.config.chunks.count({"ns": "test.foo"}), "single chunk assertion");
-assert.eq(num, primary.foo.count());
-
-s.adminCommand({shardcollection: "test.foo", key: {_id: 1}});
-
-// Make sure the collection's original chunk got split
-s.printChunks();
-assert.lte(20, findChunksUtil.countChunksForNs(s.config, "test.foo"), "many chunks assertion");
-assert.eq(num, primary.foo.count());
-
-s.printChangeLog();
-s.stop();
-})();
diff --git a/jstests/sharding/query/agg_explain_fmt.js b/jstests/sharding/query/agg_explain_fmt.js
index b11381a63d1..a1049e6f937 100644
--- a/jstests/sharding/query/agg_explain_fmt.js
+++ b/jstests/sharding/query/agg_explain_fmt.js
@@ -59,7 +59,6 @@ assert.eq(mergeCursors.nss,
"test.agg_explain_fmt",
mergeCursors); // This test manually sets collection and db at the top.
assert.eq(mergeCursors.allowPartialResults, false, mergeCursors);
-assert.eq(mergeCursors.recordRemoteOpWaitTime, false, mergeCursors);
// Do a sharded explain from a mongod, not mongos, to ensure that it does not have a
// SHARDING_FILTER stage.");
diff --git a/jstests/sharding/query/delete_with_partial_shard_key.js b/jstests/sharding/query/delete_with_partial_shard_key.js
new file mode 100644
index 00000000000..8fb85a40900
--- /dev/null
+++ b/jstests/sharding/query/delete_with_partial_shard_key.js
@@ -0,0 +1,65 @@
+/**
+ * Tests that delete queries that are not an exact match on shard key and target only a single shard
+ * work.
+ */
+
+(function() {
+'use strict';
+
+load('jstests/sharding/libs/with_partial_shard_key_util.js');
+
+const st = new ShardingTest({shards: 2});
+
+// Setup the test by creating two chunks:
+// 1. The first chunk has documents with "a" from [min, 2) and is on shard0.
+// 2. The second chunk has documents with "a" from [2, max) and is on shard1.
+const dbName = "test";
+const coll = "sharded_coll";
+const ns = dbName + "." + coll;
+const db = st.getDB(dbName);
+const docsToInsert = [{a: 1, b: 1}, {a: 2, b: 1}, {a: 3, b: 1}, {a: 999, b: 1}];
+assert.commandWorked(st.s0.adminCommand({enableSharding: dbName}));
+assert.commandWorked(st.s0.adminCommand({shardcollection: ns, key: {a: 1, b: 1}}));
+db.sharded_coll.insert(docsToInsert);
+splitAndMoveChunks(st,
+ {a: 2, b: 1} /* split point */,
+ {a: 1, b: 1} /* move chunk containing doc to shard0 */,
+ {a: 3, b: 1} /* move chunk containing doc to shard1 */);
+
+// deleteOne query without the full shard key and only one shard targeted should succeed.
+assert.eq(db.sharded_coll.count({a: 999}), 1);
+let cmdObj = {delete: coll, deletes: [{q: {a: 999}, limit: 1}]};
+assert.commandWorked(db.runCommand(cmdObj));
+assert.eq(db.sharded_coll.count({a: 999}), 0);
+assertExplainTargetsCorrectShard(db, cmdObj, st.shard1.shardName);
+
+// deleteOne query without the full shard key and multiple shards targeted should fail.
+cmdObj = {
+ delete: coll,
+ deletes: [{q: {a: {$gt: 0}}, limit: 1}]
+};
+assert.commandFailedWithCode(db.runCommand(cmdObj), ErrorCodes.ShardKeyNotFound);
+assert.commandFailedWithCode(db.runCommand({explain: cmdObj}), ErrorCodes.ShardKeyNotFound);
+
+// deleteOne query without the shard key should fail (as this would requiring targeting more than
+// one shard).
+cmdObj = {
+ delete: coll,
+ deletes: [{q: {nonShardKey: 12345}, limit: 1}]
+};
+assert.commandFailedWithCode(db.runCommand(cmdObj), ErrorCodes.ShardKeyNotFound);
+assert.commandFailedWithCode(db.runCommand({explain: cmdObj}), ErrorCodes.ShardKeyNotFound);
+
+// deleteMany query that targets only one shard should work correctly.
+assert.eq(db.sharded_coll.count({a: 1}), 1);
+cmdObj = {
+ delete: coll,
+ deletes: [{q: {a: 1}, limit: 0}]
+};
+assert.commandWorked(db.runCommand(cmdObj));
+assert.eq(db.sharded_coll.count({a: 1}), 0);
+assert.eq(db.sharded_coll.count({a: {$gt: 0}}), 2); // Check that other documents were not deleted.
+assertExplainTargetsCorrectShard(db, cmdObj, st.shard0.shardName);
+
+st.stop();
+})();
diff --git a/jstests/sharding/query/find_and_modify_with_partial_shard_key.js b/jstests/sharding/query/find_and_modify_with_partial_shard_key.js
new file mode 100644
index 00000000000..e0a0bb1b3f9
--- /dev/null
+++ b/jstests/sharding/query/find_and_modify_with_partial_shard_key.js
@@ -0,0 +1,55 @@
+/**
+ * Tests that findAndModify queries that are not an exact match on shard key and target only a
+ * single shard work.
+ */
+
+(function() {
+'use strict';
+
+load('jstests/sharding/libs/with_partial_shard_key_util.js');
+
+const st = new ShardingTest({shards: 2});
+
+// Setup the test by creating two chunks:
+// 1. The first chunk has documents with "a" from [min, 2) and is on shard0.
+// 2. The second chunk has documents with "a" from [2, max) and is on shard1.
+const dbName = "test";
+const coll = "sharded_coll";
+const ns = dbName + "." + coll;
+const db = st.getDB(dbName);
+const docsToInsert = [{a: 1, b: 1}, {a: 2, b: 1}, {a: 3, b: 1}, {a: 999, b: 1}];
+assert.commandWorked(st.s0.adminCommand({enableSharding: dbName}));
+assert.commandWorked(st.s0.adminCommand({shardcollection: ns, key: {a: 1, b: 1}}));
+db.sharded_coll.insert(docsToInsert);
+splitAndMoveChunks(st,
+ {a: 2, b: 1} /* split point */,
+ {a: 1, b: 1} /* move chunk containing doc to shard0 */,
+ {a: 3, b: 1} /* move chunk containing doc to shard1 */);
+
+// Query without the full shard key and only one shard targeted should succeed.
+assert.eq(db.sharded_coll.count({a: 1, name: "bob"}), 0);
+let cmdObj = {findAndModify: coll, query: {a: 1}, update: {$set: {name: "bob"}}};
+assert.commandWorked(db.runCommand(cmdObj));
+assert.eq(db.sharded_coll.count({a: 1, name: "bob"}), 1);
+assertExplainTargetsCorrectShard(db, cmdObj, st.shard0.shardName);
+
+// Query without the full shard key and multiple shards targeted should fail.
+cmdObj = {
+ findAndModify: coll,
+ query: {a: {$gt: 0}},
+ update: {$set: {name: "bob"}}
+};
+assert.commandFailedWithCode(db.runCommand(cmdObj), ErrorCodes.ShardKeyNotFound);
+assert.commandFailedWithCode(db.runCommand({explain: cmdObj}), ErrorCodes.ShardKeyNotFound);
+
+// Query without the shard key should fail (as this would requiring targeting more than one shard).
+cmdObj = {
+ findAndModify: coll,
+ query: {c: "3"},
+ update: {$set: {name: "bob"}}
+};
+assert.commandFailedWithCode(db.runCommand(cmdObj), ErrorCodes.ShardKeyNotFound);
+assert.commandFailedWithCode(db.runCommand({explain: cmdObj}), ErrorCodes.ShardKeyNotFound);
+
+st.stop();
+})();
diff --git a/jstests/sharding/query/lookup_graph_lookup_foreign_becomes_sharded.js b/jstests/sharding/query/lookup_graph_lookup_foreign_becomes_sharded.js
index c3924654a74..1bc05f59555 100644
--- a/jstests/sharding/query/lookup_graph_lookup_foreign_becomes_sharded.js
+++ b/jstests/sharding/query/lookup_graph_lookup_foreign_becomes_sharded.js
@@ -225,14 +225,14 @@ for (let testCase of testCases) {
const newStaleConfigErrorCount = assert.commandWorked(primaryDB.runCommand({serverStatus: 1}))
.shardingStatistics.countStaleConfigErrors;
-// ... and a single StaleConfig exception for the foreign namespace. Note that the 'ns' field of the
+// ... and a single StaleConfig error for the foreign namespace. Note that the 'ns' field of the
// profiler entry is the source collection in both cases, because the $lookup's parent aggregation
// produces the profiler entry, and it is always running on the source collection.
// TODO SERVER-60018: When the feature flag is removed, remove the check and ensure the results are
// expected.
if (!isShardedLookupEnabled) {
- // Both in the classic lookup and the SBE lookup, 'StaleConfig' error happens. In the classic
- // lookup, profiling can properly report the 'StaleConfig' of the foreign collection.
+ // Both in the classic lookup and the SBE lookup, StaleConfig error happens. In the classic
+ // lookup, profiling can properly report the StaleConfig of the foreign collection.
//
// On the other hand, in the SBE lookup, profiling fails to report the error because the
// profiling level is not set up properly according to the configured profiling level in case of
diff --git a/jstests/sharding/query/lookup_mongod_unaware.js b/jstests/sharding/query/lookup_mongod_unaware.js
index 41e5f23047b..35cc9d920cb 100644
--- a/jstests/sharding/query/lookup_mongod_unaware.js
+++ b/jstests/sharding/query/lookup_mongod_unaware.js
@@ -6,8 +6,9 @@
* We restart a mongod to cause it to forget that a collection was sharded. When restarted, we
* expect it to still have all the previous data.
*
+ * // TODO (SERVER-74380): Remove requires_fcv_70 once SERVER-74380 has been backported to v6.0
* @tags: [
- * requires_persistence
+ * requires_persistence,
* ]
*
*/
@@ -18,7 +19,7 @@ load("jstests/noPassthrough/libs/server_parameter_helpers.js"); // For setParam
load("jstests/libs/discover_topology.js"); // For findDataBearingNodes.
// Restarts the primary shard and ensures that it believes both collections are unsharded.
-function restartPrimaryShard(rs, localColl, foreignColl) {
+function restartPrimaryShard(rs, ...expectedCollections) {
// Returns true if the shard is aware that the collection is sharded.
function hasRoutingInfoForNs(shardConn, coll) {
const res = shardConn.adminCommand({getShardVersion: coll, fullMetadata: true});
@@ -28,8 +29,11 @@ function restartPrimaryShard(rs, localColl, foreignColl) {
rs.restart(0);
rs.awaitSecondaryNodes();
- assert(!hasRoutingInfoForNs(rs.getPrimary(), localColl.getFullName()));
- assert(!hasRoutingInfoForNs(rs.getPrimary(), foreignColl.getFullName()));
+
+ expectedCollections.forEach(function(coll) {
+ assert(!hasRoutingInfoForNs(rs.getPrimary(), coll.getFullName()),
+ 'Shard role not cleared for ' + coll.getFullName());
+ });
}
// Disable checking for index consistency to ensure that the config server doesn't trigger a
@@ -170,5 +174,55 @@ assert.eq(mongos0LocalColl.aggregate(pipeline).toArray(), expectedResults);
restartPrimaryShard(st.rs0, mongos0LocalColl, mongos0ForeignColl);
assert.eq(mongos1LocalColl.aggregate(pipeline).toArray(), expectedResults);
+//
+// Test two-level $lookup with a stale shard handles the shard role recovery case.
+//
+jsTest.log("Running two-level $lookup with a shard that needs recovery");
+
+assert.commandWorked(st.s0.adminCommand({enableSharding: 'D'}));
+st.ensurePrimaryShard('D', st.shard0.shardName);
+
+const D = st.s0.getDB('D');
+
+assert.commandWorked(D.A.insert({Key: 1, Value: 1}));
+assert.commandWorked(D.B.insert({Key: 1, Value: 1}));
+assert.commandWorked(D.C.insert({Key: 1, Value: 1}));
+assert.commandWorked(D.D.insert({Key: 1, Value: 1}));
+
+const aggPipeline = [{
+ $lookup: {
+ from: 'B',
+ localField: 'Key',
+ foreignField: 'Value',
+ as: 'Joined',
+ pipeline: [
+ {
+ $lookup: {
+ from: 'C',
+ localField: 'Key',
+ foreignField: 'Value',
+ as: 'Joined',
+ }
+ },
+ {
+ $lookup: {
+ from: 'D',
+ localField: 'Key',
+ foreignField: 'Value',
+ as: 'Joined',
+ }
+ },
+ ],
+ }
+}];
+
+const resultBefore = D.A.aggregate(aggPipeline).toArray();
+
+// Restarting the shard primary in order for the shard role's cache to be cleared
+restartPrimaryShard(st.rs0, D.A, D.B, D.C, D.D);
+
+const resultAfter = D.A.aggregate(aggPipeline).toArray();
+assert.eq(resultBefore, resultAfter, "Before and after results do not match");
+
st.stop();
})();
diff --git a/jstests/sharding/query/merge_write_concern.js b/jstests/sharding/query/merge_write_concern.js
index 1aac599f0f8..a29c8b7e6b8 100644
--- a/jstests/sharding/query/merge_write_concern.js
+++ b/jstests/sharding/query/merge_write_concern.js
@@ -17,12 +17,21 @@ assert.commandWorked(mongosDB.adminCommand({enableSharding: mongosDB.getName()})
st.ensurePrimaryShard(mongosDB.getName(), st.shard0.shardName);
function testWriteConcernError(rs) {
+ // Split the target collection at {_id: 10} so that there'll be doc $merge-ed to both shards.
+ if (FixtureHelpers.isSharded(target)) {
+ mongosDB.adminCommand({split: target.getFullName(), middle: {_id: 10}});
+ }
+
// Make sure that there are only 2 nodes up so w:3 writes will always time out.
const stoppedSecondary = rs.getSecondary();
rs.stop(stoppedSecondary);
// Test that $merge correctly returns a WC error.
withEachMergeMode(({whenMatchedMode, whenNotMatchedMode}) => {
+ // When either mode is "fail", a different error rather than WC error is thrown.
+ if (whenMatchedMode === "fail" || whenNotMatchedMode === "fail") {
+ return;
+ }
const res = mongosDB.runCommand({
aggregate: "source",
pipeline: [{
@@ -36,13 +45,24 @@ function testWriteConcernError(rs) {
cursor: {},
});
+ jsTestLog("Testing Mode: " + tojson(whenMatchedMode) + tojson(whenNotMatchedMode));
+ jsTestLog("Target collection after $merge: " + tojson(target.find().toArray()));
+
+ let oplogEntries = shard0.getPrimary()
+ .getDB("local")
+ .oplog.rs.find({"ns": {$regex: "merge_write_concern.*"}})
+ .toArray();
+ jsTestLog("Shard0 oplog entries: " + tojson(oplogEntries));
+ oplogEntries = shard1.getPrimary()
+ .getDB("local")
+ .oplog.rs.find({"ns": {$regex: "merge_write_concern.*"}})
+ .toArray();
+ jsTestLog("Shard1 oplog entries: " + tojson(oplogEntries));
+
// $merge writeConcern errors are handled differently from normal writeConcern
// errors. Rather than returing ok:1 and a WriteConcernError, the entire operation
// fails.
- assert.commandFailedWithCode(res,
- whenNotMatchedMode == "fail"
- ? [13113, ErrorCodes.WriteConcernFailed]
- : ErrorCodes.WriteConcernFailed);
+ assert.commandFailedWithCode(res, ErrorCodes.WriteConcernFailed);
assert.commandWorked(target.remove({}));
});
diff --git a/jstests/sharding/query/metadata_removal.js b/jstests/sharding/query/metadata_removal.js
new file mode 100644
index 00000000000..7caba6433bc
--- /dev/null
+++ b/jstests/sharding/query/metadata_removal.js
@@ -0,0 +1,46 @@
+// Ensure that metadata fields (and only metadata) are removed before documents are returned to
+// customers. As we can't insert documents with metadata, this test focuses on queries that might
+// inject metadata when communication results to mongos for merging. In addition, we want to ensure
+// that other fields beginning with a '$' are not modified before returning them to customers.
+// @tags: [
+// requires_fcv_60
+// ]
+
+(function() {
+"use strict";
+
+function runTest(coll, keyword) {
+ const document = ({_id: 5, x: 1, $set: {$inc: {x: 5}}});
+ assert.commandWorked(coll.insert(document));
+
+ // Leaves $set intact without shard merging.
+ assert.eq([document], coll.find().hint({$natural: 1}).toArray());
+ assert.eq([document], coll.find().hint({_id: 1}).toArray());
+
+ // Leaves $set intact when shard merging on agg query.
+ const docWithY = ({_id: 5, x: 1, $set: {$inc: {x: 5}}, y: 1});
+ assert.eq([docWithY], coll.aggregate([{$addFields: {y: 1}}]).toArray());
+
+ // Leaves $set intact when shard merging on sort query.
+ assert.eq([document], coll.find().sort({_id: 1}).toArray());
+
+ // Leaves added fields intact when user adds $ prefixed field in an agg pipeline.
+ const docWithDollarY = ({_id: 5, x: 1, $set: {$inc: {x: 5}}, $y: 1});
+ assert.eq(
+ [docWithDollarY],
+ coll.aggregate(
+ [{$replaceWith: {$setField: {field: {$literal: "$y"}, input: "$$ROOT", value: 1}}}])
+ .toArray());
+}
+
+const st = new ShardingTest({shards: 2});
+try {
+ assert.commandWorked(st.s0.adminCommand({enableSharding: 'test'}));
+ st.ensurePrimaryShard('test', st.shard1.shardName);
+ assert.commandWorked(st.s0.adminCommand({shardCollection: 'test.coll', key: {x: 'hashed'}}));
+
+ runTest(st.getDB("test").coll);
+} finally {
+ st.stop();
+}
+})();
diff --git a/jstests/sharding/query/shard_refuses_cursor_ownership.js b/jstests/sharding/query/shard_refuses_cursor_ownership.js
new file mode 100644
index 00000000000..30e6e88d896
--- /dev/null
+++ b/jstests/sharding/query/shard_refuses_cursor_ownership.js
@@ -0,0 +1,83 @@
+/**
+ * This test runs and unsharded $out query which results in mongos setting up a cursor and giving
+ * it to a shard to complete. mongos assumes the shard will kill the cursor, but if a shard doesn't
+ * accept ownership of the cursor then previously no one would kill it. this test ensures mongos
+ * will kill the cursor if a shard doesn't accept ownership.
+ * @tags: [
+ * ]
+ */
+(function() {
+"use strict";
+
+load("jstests/libs/fail_point_util.js");
+load("jstests/libs/parallel_shell_helpers.js");
+
+const st = new ShardingTest({shards: 2});
+
+const dbName = jsTestName();
+const collName = "foo";
+const ns = dbName + "." + collName;
+
+let db = st.s.getDB(dbName);
+assert.commandWorked(db.dropDatabase());
+let coll = db[collName];
+
+st.shardColl(collName, {x: 1}, {x: 0}, {x: 1}, dbName, true);
+
+assert.commandWorked(coll.insert([{x: -2}, {x: -1}, {x: 1}, {x: 2}]));
+
+const primary = st.getPrimaryShard(dbName);
+const other = st.getOther(st.getPrimaryShard(dbName));
+
+// Start an aggregation that requires merging on a shard. Let it run until the shard cursors have
+// been established but make it hang right before opening the merge cursor.
+let shardedAggregateHangBeforeDispatchMergingPipelineFP =
+ configureFailPoint(st.s, "shardedAggregateHangBeforeDispatchMergingPipeline");
+let awaitAggregationShell = startParallelShell(
+ funWithArgs((dbName, collName) => {
+ assert.eq(
+ 0, db.getSiblingDB(dbName)[collName].aggregate([{$out: collName + ".out"}]).itcount());
+ }, dbName, collName), st.s.port);
+shardedAggregateHangBeforeDispatchMergingPipelineFP.wait();
+
+// Start a chunk migration, let it run until it enters the critical section.
+let hangBeforePostMigrationCommitRefresh =
+ configureFailPoint(primary, "hangBeforePostMigrationCommitRefresh");
+let awaitMoveChunkShell = startParallelShell(
+ funWithArgs((recipientShard, ns) => {
+ assert.commandWorked(db.adminCommand({moveChunk: ns, find: {x: -1}, to: recipientShard}));
+ }, other.shardName, ns), st.s.port);
+hangBeforePostMigrationCommitRefresh.wait();
+
+// Let the aggregation continue and try to establish the merge cursor (it will first fail because
+// the shard is in the critical section. Mongos will transparently retry).
+shardedAggregateHangBeforeDispatchMergingPipelineFP.off();
+
+// Let the migration exit the critical section and complete.
+hangBeforePostMigrationCommitRefresh.off();
+
+// The aggregation will be able to complete now.
+awaitAggregationShell();
+
+awaitMoveChunkShell();
+
+// Did any cursor leak?
+const idleCursors = primary.getDB("admin")
+ .aggregate([
+ {$currentOp: {idleCursors: true, allUsers: true}},
+ {$match: {type: "idleCursor", ns: ns}}
+ ])
+ .toArray();
+assert.eq(0, idleCursors.length, "Found idle cursors: " + tojson(idleCursors));
+
+// Check that range deletions can be completed (if a cursor was left open, the range deletion would
+// not finish).
+assert.soon(
+ () => {
+ return primary.getDB("config")["rangeDeletions"].find().itcount() === 0;
+ },
+ "Range deletion tasks did not finish: + " +
+ tojson(primary.getDB("config")["rangeDeletions"].find().toArray()));
+
+st.stop();
+})();
diff --git a/jstests/sharding/read_write_concern_defaults_application.js b/jstests/sharding/read_write_concern_defaults_application.js
index 9b8b3a1efda..1f6effbcdcf 100644
--- a/jstests/sharding/read_write_concern_defaults_application.js
+++ b/jstests/sharding/read_write_concern_defaults_application.js
@@ -125,6 +125,7 @@ let testCases = {
_configsvrShardCollection:
{skip: "internal command"}, // TODO SERVER-58843: Remove once 6.0 becomes last LTS
_configsvrUpdateZoneKeyRange: {skip: "internal command"},
+ _dropConnectionsToMongot: {skip: "internal command"},
_flushDatabaseCacheUpdates: {skip: "internal command"},
_flushDatabaseCacheUpdatesWithWriteConcern: {skip: "internal command"},
_flushReshardingStateChange: {skip: "internal command"},
@@ -138,6 +139,7 @@ let testCases = {
_killOperations: {skip: "internal command"},
_mergeAuthzCollections: {skip: "internal command"},
_migrateClone: {skip: "internal command"},
+ _mongotConnPoolStats: {skip: "internal command"},
_recvChunkAbort: {skip: "internal command"},
_recvChunkCommit: {skip: "internal command"},
_recvChunkReleaseCritSec: {skip: "internal command"},
@@ -340,6 +342,7 @@ let testCases = {
shardedTargetsConfigServer: true,
useLogs: true,
},
+ createSearchIndexes: {skip: "does not accept read or write concern"},
createUser: {
command: {createUser: "foo", pwd: "bar", roles: []},
checkReadConcern: false,
@@ -440,6 +443,7 @@ let testCases = {
shardedTargetsConfigServer: true,
useLogs: true,
},
+ dropSearchIndex: {skip: "does not accept read or write concern"},
dropUser: {
setUp: function(conn) {
assert.commandWorked(conn.getDB(db).runCommand(
@@ -486,7 +490,6 @@ let testCases = {
getDatabaseVersion: {skip: "does not accept read or write concern"},
getDefaultRWConcern: {skip: "does not accept read or write concern"},
getDiagnosticData: {skip: "does not accept read or write concern"},
- getFreeMonitoringStatus: {skip: "does not accept read or write concern"},
getLastError: {skip: "does not accept read or write concern"},
getLog: {skip: "does not accept read or write concern"},
getMore: {skip: "does not accept read or write concern"},
@@ -562,6 +565,7 @@ let testCases = {
listCommands: {skip: "does not accept read or write concern"},
listDatabases: {skip: "does not accept read or write concern"},
listIndexes: {skip: "does not accept read or write concern"},
+ listSearchIndexes: {skip: "does not accept read or write concern"},
listShards: {skip: "does not accept read or write concern"},
lockInfo: {skip: "does not accept read or write concern"},
logApplicationMessage: {skip: "does not accept read or write concern"},
@@ -611,7 +615,6 @@ let testCases = {
checkReadConcern: false,
checkWriteConcern: true,
},
- repairDatabase: {skip: "does not accept read or write concern"},
repairShardedCollectionChunksHistory: {skip: "does not accept read or write concern"},
replSetAbortPrimaryCatchUp: {skip: "does not accept read or write concern"},
replSetFreeze: {skip: "does not accept read or write concern"},
@@ -697,7 +700,7 @@ let testCases = {
setCommittedSnapshot: {skip: "internal command"},
setDefaultRWConcern: {skip: "special case (must run after all other commands)"},
setFeatureCompatibilityVersion: {skip: "does not accept read or write concern"},
- setFreeMonitoring: {skip: "does not accept read or write concern"},
+ setProfilingFilterGlobally: {skip: "does not accept read or write concern"},
setIndexCommitQuorum: {skip: "does not accept read or write concern"},
setParameter: {skip: "does not accept read or write concern"},
setShardVersion: {skip: "internal command"},
@@ -744,6 +747,7 @@ let testCases = {
shardedTargetsConfigServer: true,
useLogs: true,
},
+ updateSearchIndex: {skip: "does not accept read or write concern"},
updateUser: {
setUp: function(conn) {
assert.commandWorked(conn.getDB(db).runCommand(
@@ -889,6 +893,11 @@ function createProfileFilterForTestCase(test, targetId, explicitRWC) {
function runScenario(
desc, conn, regularCheckConn, configSvrCheckConn, {explicitRWC, explicitProvenance = false}) {
let runCommandTest = function(cmdName, test) {
+ // These commands were removed but break this test in multiversion
+ if (cmdName === "getFreeMonitoringStatus" || cmdName === "setFreeMonitoring") {
+ return;
+ }
+
assert(test !== undefined,
"coverage failure: must define a RWC defaults application test for " + cmdName);
diff --git a/jstests/sharding/refine_collection_shard_key_basic.js b/jstests/sharding/refine_collection_shard_key_basic.js
index 671493d99ee..7cacc2b6d55 100644
--- a/jstests/sharding/refine_collection_shard_key_basic.js
+++ b/jstests/sharding/refine_collection_shard_key_basic.js
@@ -1,6 +1,8 @@
//
// Basic tests for refineCollectionShardKey.
//
+// Disabled in multiversion, see SERVER-69290 for more details.
+// @tags: [multiversion_incompatible]
(function() {
'use strict';
@@ -125,11 +127,9 @@ function validateCRUDAfterRefine() {
assert.eq(4, sessionDB.getCollection(kCollName).findOne({c: -1}).b);
mongos.setReadPref(null);
- // The full shard key is required when removing documents.
- assert.writeErrorWithCode(sessionDB.getCollection(kCollName).remove({a: 1, b: 1}, true),
- ErrorCodes.ShardKeyNotFound);
- assert.writeErrorWithCode(sessionDB.getCollection(kCollName).remove({a: -1, b: -1}, true),
- ErrorCodes.ShardKeyNotFound);
+ assert.commandWorked(sessionDB.getCollection(kCollName).remove({a: 1, b: 1}, true));
+ assert.commandWorked(sessionDB.getCollection(kCollName).remove({a: -1, b: -1}, true));
+
assert.commandWorked(sessionDB.getCollection(kCollName).remove({a: 1, b: 2, c: 1, d: 1}, true));
assert.commandWorked(
sessionDB.getCollection(kCollName).remove({a: -1, b: 4, c: -1, d: -1}, true));
diff --git a/jstests/sharding/rename.js b/jstests/sharding/rename.js
index 4759127de02..472b333ff43 100644
--- a/jstests/sharding/rename.js
+++ b/jstests/sharding/rename.js
@@ -37,30 +37,18 @@ assert.commandWorked(
assert.commandFailed(db.bar.renameCollection('shardedColl'));
// Renaming unsharded collection to a different db with different primary shard.
-db.unSharded.insert({x: 1});
+db.unsharded.insert({x: 1});
assert.commandFailedWithCode(
- db.adminCommand({renameCollection: 'test.unSharded', to: 'otherDBDifferentPrimary.foo'}),
+ db.adminCommand({renameCollection: 'test.unsharded', to: 'otherDBDifferentPrimary.foo'}),
[ErrorCodes.CommandFailed],
"Source and destination collections must be on the same database.");
// Renaming unsharded collection to a different db with same primary shard.
assert.commandWorked(
- db.adminCommand({renameCollection: 'test.unSharded', to: 'otherDBSamePrimary.foo'}));
+ db.adminCommand({renameCollection: 'test.unsharded', to: 'otherDBSamePrimary.foo'}));
assert.eq(0, db.unsharded.countDocuments({}));
assert.eq(1, s.getDB('otherDBSamePrimary').foo.countDocuments({}));
-jsTest.log("Testing that rename operations involving views are not allowed");
-{
- assert.commandWorked(db.collForView.insert({_id: 1}));
- assert.commandWorked(db.createView('view', 'collForView', []));
-
- let toAView = db.unsharded.renameCollection('view', true /* dropTarget */);
- assert.commandFailed(toAView);
-
- let fromAView = db.view.renameCollection('target');
- assert.commandFailed(fromAView);
-}
-
// Rename a collection to itself fails, without loosing data
{
const sameCollName = 'sameColl';
@@ -73,6 +61,35 @@ jsTest.log("Testing that rename operations involving views are not allowed");
assert.eq(1, sameColl.countDocuments({}), "Rename a collection to itself must not loose data");
}
+const testDB = s.rs0.getPrimary().getDB('test');
+const fcvDoc = testDB.adminCommand({getParameter: 1, featureCompatibilityVersion: 1});
+if (MongoRunner.compareBinVersions(fcvDoc.featureCompatibilityVersion.version, '6.0') >= 0) {
+ // Create collection on non-primary shard (shard1 for test db) to simulate wrong creation via
+ // direct connection: collection rename should fail since `badcollection` uuids are inconsistent
+ // across shards
+ jsTest.log("Testing uuid consistency across shards");
+ assert.commandWorked(
+ s.shard1.getDB('test').badcollection.insert({_id: 1})); // direct connection
+ assert.commandWorked(s.s0.getDB('test').badcollection.insert({_id: 1})); // mongos connection
+ assert.commandFailedWithCode(
+ s.s0.getDB('test').badcollection.renameCollection('goodcollection'),
+ [ErrorCodes.InvalidUUID],
+ "collection rename should fail since test.badcollection uuids are inconsistent across shards");
+
+ // Target collection existing on non-primary shard: rename with `dropTarget=false` must fail
+ jsTest.log(
+ "Testing rename behavior when target collection [wrongly] exists on non-primary shards");
+ assert.commandWorked(
+ s.shard1.getDB('test').superbadcollection.insert({_id: 1})); // direct connection
+ assert.commandWorked(s.s0.getDB('test').goodcollection.insert({_id: 1})); // mongos connection
+ assert.commandFailedWithCode(
+ s.s0.getDB('test').goodcollection.renameCollection('superbadcollection', false),
+ [ErrorCodes.NamespaceExists],
+ "Collection rename with `dropTarget=false` must have failed because target collection exists on a non-primary shard");
+ // Target collection existing on non-primary shard: rename with `dropTarget=true` must succeed
+ assert.commandWorked(
+ s.s0.getDB('test').goodcollection.renameCollection('superbadcollection', true));
+}
// Ensure write concern works by shutting down 1 node in a replica set shard
jsTest.log("Testing write concern (2)");
diff --git a/jstests/sharding/rename_sharded.js b/jstests/sharding/rename_sharded.js
index 35764906362..6112bd2c9a4 100644
--- a/jstests/sharding/rename_sharded.js
+++ b/jstests/sharding/rename_sharded.js
@@ -59,11 +59,37 @@ const st = new ShardingTest({shards: 3, mongos: 1, other: {enableBalancer: false
const mongos = st.s0;
-// Rename to non-existing target collection must succeed
+// Rename non-existing source collection to a target collection/view (dropTarget=false) must
+// fail with NamespaceNotFound. Make sure the check on the source is done before any check on the
+// target for consistency with replicaset. We cannot delegate this to passthrough suite since in
+// those suites any non-existing collection will always be implicitely sharded at the first access
{
- const dbName = 'testRenameToNewCollection';
- const toNs = dbName + '.to';
- testRename(st, dbName, toNs, false /* dropTarget */, false /* mustFail */);
+ const dbName = 'notExistingSource';
+ assert.commandWorked(
+ mongos.adminCommand({enablesharding: dbName, primaryShard: st.shard0.shardName}));
+
+ // Rename non-existing source to non-existing target
+ assert.commandFailedWithCode(
+ st.getDB(dbName).adminCommand(
+ {renameCollection: dbName + ".source", to: dbName + ".target"}),
+ ErrorCodes.NamespaceNotFound);
+
+ // Rename non-existing source to existing collection
+ const toCollName = dbName + ".target";
+ const toColl = mongos.getCollection(toCollName);
+ toColl.insert({a: 0});
+
+ assert.commandFailedWithCode(
+ st.getDB(dbName).adminCommand({renameCollection: dbName + ".source", to: toCollName}),
+ ErrorCodes.NamespaceNotFound);
+
+ // Rename non-existing source to existing view
+ const toViewName = dbName + ".target_view";
+ assert.commandWorked(st.getDB(dbName).createView(toViewName, toCollName, []));
+
+ assert.commandFailedWithCode(
+ st.getDB(dbName).adminCommand({renameCollection: dbName + ".source", to: toViewName}),
+ ErrorCodes.NamespaceNotFound);
}
// Rename to existing sharded target collection with dropTarget=true must succeed
@@ -114,19 +140,6 @@ const mongos = st.s0;
testRename(st, dbName, toNs, false /* dropTarget */, true /* mustFail */);
}
-// Rename to existing sharded target collection with dropTarget=false must fail
-{
- const dbName = 'testRenameToShardedCollectionWithoutDropTarget';
- const toNs = dbName + '.to';
- assert.commandWorked(
- mongos.adminCommand({enablesharding: dbName, primaryShard: st.shard0.shardName}));
- assert.commandWorked(mongos.adminCommand({shardCollection: toNs, key: {a: 1}}));
- const toColl = mongos.getCollection(toNs);
- toColl.insert({a: 0});
-
- testRename(st, dbName, toNs, false /* dropTarget */, true /* mustFail */);
-}
-
// Rename unsharded collection to sharded target collection with dropTarget=true must succeed
{
const dbName = 'testRenameUnshardedToShardedTargetCollection';
diff --git a/jstests/sharding/replication_with_undefined_shard_key.js b/jstests/sharding/replication_with_undefined_shard_key.js
index f684bd29e29..a363ff06b9e 100644
--- a/jstests/sharding/replication_with_undefined_shard_key.js
+++ b/jstests/sharding/replication_with_undefined_shard_key.js
@@ -22,9 +22,9 @@ jsTestLog("Doing writes that generate oplog entries including undefined document
assert.commandWorked(mongosColl.update(
{},
{$set: {a: 1}},
- {multi: true, writeConcern: {w: 2, wtimeout: ReplSetTest.kDefaultTimeoutMs}}));
+ {multi: true, writeConcern: {w: 2, wtimeout: ReplSetTest.kDefaultTimeoutMS}}));
assert.commandWorked(
- mongosColl.remove({}, {writeConcern: {w: 2, wtimeout: ReplSetTest.kDefaultTimeoutMs}}));
+ mongosColl.remove({}, {writeConcern: {w: 2, wtimeout: ReplSetTest.kDefaultTimeoutMS}}));
st.stop();
})(); \ No newline at end of file
diff --git a/jstests/sharding/resharding_abort_command.js b/jstests/sharding/resharding_abort_command.js
index f8e7aeaf9f5..7c5f4a95cb0 100644
--- a/jstests/sharding/resharding_abort_command.js
+++ b/jstests/sharding/resharding_abort_command.js
@@ -177,7 +177,7 @@ const runAbortWithFailpoint = (failpointName, failpointNodeType, abortLocation,
// Resharding has not been attempted yet, so resharding metrics will not be reported. This means
// shardingStatistics will be empty, and thus not reported. So we assert that the serverStatus
// does not have shardingStatistics yet.
- assert(!status.hasOwnProperty('shardingStatistics'), status);
+ assert(!status.shardingStatistics.hasOwnProperty("resharding"), status);
let expectedAbortErrorCodes = ErrorCodes.OK;
let expectedReshardingErrorCode = ErrorCodes.ReshardCollectionAborted;
diff --git a/jstests/sharding/resharding_abort_in_preparing_to_donate.js b/jstests/sharding/resharding_abort_in_preparing_to_donate.js
index 167dcd3c67a..711dbb71860 100644
--- a/jstests/sharding/resharding_abort_in_preparing_to_donate.js
+++ b/jstests/sharding/resharding_abort_in_preparing_to_donate.js
@@ -11,6 +11,7 @@
"use strict";
load("jstests/libs/discover_topology.js");
load("jstests/sharding/libs/resharding_test_fixture.js");
+load('jstests/libs/parallel_shell_helpers.js');
const originalCollectionNs = "reshardingDb.coll";
@@ -36,6 +37,7 @@ const configsvr = new Mongo(topology.configsvr.nodes[0]);
const pauseAfterPreparingToDonateFP =
configureFailPoint(configsvr, "reshardingPauseCoordinatorAfterPreparingToDonate");
+let awaitAbort;
reshardingTest.withReshardingInBackground(
{
@@ -47,13 +49,30 @@ reshardingTest.withReshardingInBackground(
},
() => {
pauseAfterPreparingToDonateFP.wait();
- assert.commandWorked(mongos.adminCommand({abortReshardCollection: originalCollectionNs}));
+ assert.neq(null, mongos.getCollection("config.reshardingOperations").findOne({
+ ns: originalCollectionNs
+ }));
// Signaling abort will cause the
// pauseAfterPreparingToDonateFP to throw, implicitly
// allowing the coordinator to make progress without
// explicitly turning off the failpoint.
+ awaitAbort =
+ startParallelShell(funWithArgs(function(sourceNamespace) {
+ db.adminCommand({abortReshardCollection: sourceNamespace});
+ }, originalCollectionNs), mongos.port);
+ // Wait for the coordinator to remove coordinator document from config.reshardingOperations
+ // as a result of the recipients and donors transitioning to done due to abort.
+ assert.soon(() => {
+ const coordinatorDoc = mongos.getCollection("config.reshardingOperations").findOne({
+ ns: originalCollectionNs
+ });
+ return coordinatorDoc === null || coordinatorDoc.state === "aborting";
+ });
},
{expectedErrorCode: ErrorCodes.ReshardCollectionAborted});
+
+awaitAbort();
pauseAfterPreparingToDonateFP.off();
+
reshardingTest.teardown();
})();
diff --git a/jstests/sharding/resharding_coordinator_recovers_abort_decision.js b/jstests/sharding/resharding_coordinator_recovers_abort_decision.js
index 80e0ef14b77..d7b13c2b068 100644
--- a/jstests/sharding/resharding_coordinator_recovers_abort_decision.js
+++ b/jstests/sharding/resharding_coordinator_recovers_abort_decision.js
@@ -21,7 +21,8 @@ const sourceCollection = reshardingTest.createShardedCollection({
});
const mongos = sourceCollection.getMongo();
-const topology = DiscoverTopology.findConnectedNodes(mongos);
+const ns = sourceCollection.getFullName();
+let topology = DiscoverTopology.findConnectedNodes(mongos);
const recipientShardNames = reshardingTest.recipientShardNames;
const recipient = new Mongo(topology.shards[recipientShardNames[0]].primary);
@@ -38,6 +39,13 @@ const shardsvrAbortReshardCollectionFailpoint = configureFailPoint(recipient, "f
failCommands: ["_shardsvrAbortReshardCollection"],
});
+// We pause the _configsvrReshardCollection command upon joining an existing ReshardingCoordinator
+// instance on all of the config server replica set because we don't know which node will be elected
+// primary from calling stepUpNewPrimaryOnShard().
+const configsvrConnections = topology.configsvr.nodes.map(host => new Mongo(host));
+const reshardCollectionJoinedFailPointsList = configsvrConnections.map(
+ conn => configureFailPoint(conn, "reshardCollectionJoinedExistingOperation"));
+
let awaitAbort;
reshardingTest.withReshardingInBackground(
{
@@ -48,7 +56,6 @@ reshardingTest.withReshardingInBackground(
// Wait until participants are aware of the resharding operation.
reshardingTest.awaitCloneTimestampChosen();
- const ns = sourceCollection.getFullName();
awaitAbort = startParallelShell(funWithArgs(function(ns) {
db.adminCommand({abortReshardCollection: ns});
}, ns), mongos.port);
@@ -70,13 +77,12 @@ reshardingTest.withReshardingInBackground(
// Mongos automatically retries the abortReshardCollection command on retryable errors.
// We interrupt the abortReshardCollection command running on mongos to verify that the
// ReshardingCoordinator recovers the decision on its own.
- const ops =
- mongos.getDB("admin")
- .aggregate([
- {$currentOp: {localOps: true}},
- {$match: {"command.abortReshardCollection": sourceCollection.getFullName()}}
- ])
- .toArray();
+ const ops = mongos.getDB("admin")
+ .aggregate([
+ {$currentOp: {localOps: true}},
+ {$match: {"command.abortReshardCollection": ns}}
+ ])
+ .toArray();
assert.neq([], ops, "failed to find abortReshardCollection command running on mongos");
assert.eq(
@@ -88,6 +94,29 @@ reshardingTest.withReshardingInBackground(
assert.commandWorked(mongos.getDB("admin").killOp(ops[0].opid));
reshardingTest.stepUpNewPrimaryOnShard(reshardingTest.configShardName);
+
+ // After a stepdown, the _configsvrReshardCollection command will be retried by the
+ // primary shard. We use the reshardCollectionJoinedExistingOperation failpoint to
+ // ensure the primary shard upon retrying finds the ongoing resharding operation on the
+ // new config server primary. It would otherwise be possible for the
+ // reshardingPauseCoordinatorBeforeCompletion failpoint to be released by the
+ // ReshardingTest fixture after this function returns, for the ongoing resharding
+ // operation to complete, and for the retried _configsvrReshardCollection command to
+ // spawn an entirely new resharding operation which won't get aborted by the test
+ // client.
+ topology = DiscoverTopology.findConnectedNodes(mongos);
+ const configsvrPrimary = new Mongo(topology.configsvr.primary);
+ const idx = reshardCollectionJoinedFailPointsList.findIndex(fp => fp.conn.host ===
+ configsvrPrimary.host);
+ reshardCollectionJoinedFailPointsList[idx].wait();
+
+ // Wait for secondaries to recover and catchup with primary before turning off the
+ // failpoints as a replication roll back can disconnect the test client.
+ const configRS = reshardingTest.getReplSetForShard(reshardingTest.configShardName);
+ configRS.awaitSecondaryNodes();
+ configRS.awaitReplication();
+ reshardCollectionJoinedFailPointsList.forEach(
+ fp => reshardingTest.retryOnceOnNetworkError(fp.off));
shardsvrAbortReshardCollectionFailpoint.off();
},
});
diff --git a/jstests/sharding/resharding_feature_flagging.js b/jstests/sharding/resharding_feature_flagging.js
index 6e964a2237c..8b023168865 100644
--- a/jstests/sharding/resharding_feature_flagging.js
+++ b/jstests/sharding/resharding_feature_flagging.js
@@ -46,7 +46,7 @@ assert.commandFailedWithCode(
const serverStatusCmd = ({serverStatus: 1, shardingStatistics: 1});
let res = assert.commandWorked(configPrimary.adminCommand(serverStatusCmd));
-assert(!res.hasOwnProperty("shardingStatistics"), res.shardingStatistics);
+assert(!res.shardingStatistics.hasOwnProperty("resharding"), res.shardingStatistics);
const shardPrimary = st.shard0.rs.getPrimary();
res = assert.commandWorked(shardPrimary.adminCommand(serverStatusCmd));
diff --git a/jstests/sharding/resharding_interrupt_before_create_state_machine.js b/jstests/sharding/resharding_interrupt_before_create_state_machine.js
new file mode 100644
index 00000000000..8b18d498ff2
--- /dev/null
+++ b/jstests/sharding/resharding_interrupt_before_create_state_machine.js
@@ -0,0 +1,46 @@
+/**
+ * Test that reshardCollection does not hang if its opCtx is interrupted between inserting the state
+ * document for its state machine and starting that state machine. See SERVER-74647.
+ */
+(function() {
+"use strict";
+load("jstests/sharding/libs/resharding_test_fixture.js");
+load("jstests/libs/fail_point_util.js");
+load("jstests/libs/discover_topology.js");
+
+const sourceNs = "reshardingDb.coll";
+
+const reshardingTest = new ReshardingTest();
+reshardingTest.setup();
+
+const donorShardNames = reshardingTest.donorShardNames;
+const recipientShardNames = reshardingTest.recipientShardNames;
+
+const inputCollection = reshardingTest.createShardedCollection({
+ ns: sourceNs,
+ shardKeyPattern: {oldKey: 1},
+ chunks: [
+ {min: {oldKey: MinKey}, max: {oldKey: MaxKey}, shard: donorShardNames[0]},
+ ],
+});
+
+const mongos = inputCollection.getMongo();
+const topology = DiscoverTopology.findConnectedNodes(mongos);
+const donorPrimary = new Mongo(topology.shards[donorShardNames[0]].primary);
+
+const failpoint =
+ configureFailPoint(donorPrimary, "reshardingInterruptAfterInsertStateMachineDocument");
+
+reshardingTest.withReshardingInBackground({
+ newShardKeyPattern: {newKey: 1},
+ newChunks: [
+ {min: {newKey: MinKey}, max: {newKey: MaxKey}, shard: recipientShardNames[0]},
+ ]
+},
+ () => {
+ failpoint.wait();
+ failpoint.off();
+ });
+
+reshardingTest.teardown();
+})();
diff --git a/jstests/sharding/resharding_large_number_of_initial_chunks.js b/jstests/sharding/resharding_large_number_of_initial_chunks.js
index 798131c0fb8..0bda116b9c7 100644
--- a/jstests/sharding/resharding_large_number_of_initial_chunks.js
+++ b/jstests/sharding/resharding_large_number_of_initial_chunks.js
@@ -1,6 +1,6 @@
/**
- * Tests that resharding can complete successfully when the original collection has a large number
- * of chunks.
+ * Tests that resharding can complete successfully when it has a large number
+ * of chunks being created during the process.
*
* @tags: [
* uses_atclustertime,
@@ -29,41 +29,42 @@ const kDbName = 'db';
const collName = 'foo';
const ns = kDbName + '.' + collName;
const mongos = st.s;
+const shard0 = st.shard0.shardName;
+const shard1 = st.shard1.shardName;
assert.commandWorked(mongos.adminCommand({enableSharding: kDbName}));
assert.commandWorked(mongos.adminCommand({shardCollection: ns, key: {oldKey: 1}}));
-let nZones = 10000;
-let zones = [];
-let shard0Zones = [];
-let shard1Zones = [];
-for (let i = 0; i < nZones; i++) {
- let zoneName = "zone" + i;
- zones.push({zone: zoneName, min: {"newKey": i}, max: {"newKey": i + 1}});
+let nChunks = 100000;
+let newChunks = [];
+newChunks.push({min: {newKey: MinKey}, max: {newKey: 0}, recipientShardId: shard0});
+for (let i = 0; i < nChunks; i++) {
if (i % 2 == 0) {
- shard0Zones.push(zoneName);
+ newChunks.push({min: {newKey: i}, max: {newKey: i + 1}, recipientShardId: shard0});
} else {
- shard1Zones.push(zoneName);
+ newChunks.push({min: {newKey: i}, max: {newKey: i + 1}, recipientShardId: shard1});
}
}
-
-jsTestLog("Updating First Zone");
-assert.commandWorked(
- mongos.getDB("config").shards.update({_id: st.shard0.shardName}, {$set: {tags: shard0Zones}}));
-jsTestLog("Updating First Zone");
-assert.commandWorked(
- mongos.getDB("config").shards.update({_id: st.shard1.shardName}, {$set: {tags: shard1Zones}}));
+newChunks.push({min: {newKey: nChunks}, max: {newKey: MaxKey}, recipientShardId: shard1});
jsTestLog("Resharding Collection");
-assert.commandWorked(mongos.adminCommand({reshardCollection: ns, key: {newKey: 1}, zones: zones}));
+assert.commandWorked(mongos.adminCommand(
+ {reshardCollection: ns, key: {newKey: 1}, _presetReshardedChunks: newChunks}));
-// Assert that the correct number of zones and chunks documents exist after resharding 'db.foo'.
-// There should be two more chunks docs than zones docs created to cover the ranges
-// {newKey: minKey -> newKey : 0} and {newKey: nZones -> newKey : maxKey} which are not associated
-// with a zone.
-assert.eq(mongos.getDB("config").tags.find({ns: ns}).itcount(), nZones);
-assert.eq(findChunksUtil.countChunksForNs(mongos.getDB("config"), ns), nZones + 2);
+// Assert that the correct number of chunks documents exist after resharding 'db.foo'.
+// There should be two more chunks docs to cover the ranges
+// {newKey: minKey -> newKey : 0} and {newKey: nChunks -> newKey : maxKey}
+assert.eq(findChunksUtil.countChunksForNs(mongos.getDB("config"), ns), nChunks + 2);
+// check_orphans_are_deleted.js is skipped because it takes 1 minute to run on an optimized build
+// and this test doesn't insert any data for there to be unowned documents anyway.
+TestData.skipCheckOrphans = true;
+// check_uuids_consistent_across_cluster.js is skipped because it takes nearly 1 minute to run on an
+// optimized build.
+TestData.skipCheckingUUIDsConsistentAcrossCluster = true;
+// check_routing_table_consistency.js is skipped because its $group + $lookup aggregation over the
+// config.chunks documents exceeds 100MB and fails.
+TestData.skipCheckRoutingTableConsistency = true;
st.stop();
})();
diff --git a/jstests/sharding/resharding_metrics.js b/jstests/sharding/resharding_metrics.js
index c4cedb11711..7e1d3a830a2 100644
--- a/jstests/sharding/resharding_metrics.js
+++ b/jstests/sharding/resharding_metrics.js
@@ -50,8 +50,7 @@ function testMetricsArePresent(mongo, expectedMetrics, minOplogEntriesFetchedAnd
function verifyStatsMissing(mongo) {
const stats = mongo.getDB('admin').serverStatus({});
- assert(!stats.hasOwnProperty('shardingStatistics') ||
- !stats.shardingStatistics.hasOwnProperty('resharding'),
+ assert(!stats.shardingStatistics.hasOwnProperty('resharding'),
`Resharding section not expected in ${tojson(stats)}`);
}
diff --git a/jstests/sharding/resharding_nonblocking_coordinator_rebuild.js b/jstests/sharding/resharding_nonblocking_coordinator_rebuild.js
index dac1afc0014..2ee6c76aaf1 100644
--- a/jstests/sharding/resharding_nonblocking_coordinator_rebuild.js
+++ b/jstests/sharding/resharding_nonblocking_coordinator_rebuild.js
@@ -109,19 +109,7 @@ reshardingTest.withReshardingInBackground(
}
},
{
- // As a result of the elections intentionally triggered on the config server replica sets,
- // the primary shard of the database may retry the _configsvrReshardCollection command. It
- // is possible for the resharding operation from the first _configsvrReshardCollection
- // command to have entirely finished executing to the point of removing the coordinator
- // state document. A retry of the _configsvrReshardCollection command in this situation will
- // lead to a second resharding operation to run. The second resharding operation will have
- // the duplicate documents cloned by the ReshardingCollectionCloner rather than applied by
- // the ReshardingOplogApplier as intended. This results in the reshardCollection command
- // failing with a DuplicateKey error rather than the error code for the stash collections
- // being non-empty. The recipient must have been able to successfully update its state to
- // "applying" in the first resharding operation even when the ReshardingCoordinatorService
- // had yet to be rebuilt so we accept DuplicateKey as an error too.
- expectedErrorCode: [5356800, ErrorCodes.DuplicateKey],
+ expectedErrorCode: 5356800,
});
reshardingTest.teardown();
diff --git a/jstests/sharding/resharding_prohibited_commands.js b/jstests/sharding/resharding_prohibited_commands.js
index d06a9561d2a..9f7d73b57c3 100644
--- a/jstests/sharding/resharding_prohibited_commands.js
+++ b/jstests/sharding/resharding_prohibited_commands.js
@@ -118,6 +118,7 @@ const waitUntilReshardingInitializedOnDonor = () => {
* @param {Function} config.setup
* @param {AfterReshardingCallback} afterReshardingFn
*/
+
const withReshardingInBackground =
(duringReshardingFn,
{setup = () => {}, expectedErrorCode, afterReshardingFn = () => {}} = {}) => {
@@ -132,22 +133,34 @@ const withReshardingInBackground =
},
duringReshardingFn,
{expectedErrorCode: expectedErrorCode, afterReshardingFn: afterReshardingFn});
-
assertCommandsSucceedAfterReshardingOpFinishes(mongos.getDB(databaseName));
assert.commandWorked(sourceCollection.dropIndex(indexCreatedByTest));
};
// Tests that the prohibited commands work if the resharding operation is aborted.
+let awaitAbort;
withReshardingInBackground(() => {
waitUntilReshardingInitializedOnDonor();
+ assert.neq(null,
+ mongos.getCollection("config.reshardingOperations").findOne({ns: sourceNamespace}));
+ awaitAbort = startParallelShell(funWithArgs(function(sourceNamespace) {
+ db.adminCommand({abortReshardCollection: sourceNamespace});
+ }, sourceNamespace), mongos.port);
+ // Wait for the coordinator to remove coordinator document from config.reshardingOperations
+ // as a result of the recipients and donors transitioning to done due to abort.
+ assert.soon(() => {
+ const coordinatorDoc =
+ mongos.getCollection("config.reshardingOperations").findOne({ns: sourceNamespace});
- assert.commandWorked(mongos.adminCommand({abortReshardCollection: sourceNamespace}));
+ return coordinatorDoc === null || coordinatorDoc.state === "aborting";
+ });
}, {
expectedErrorCode: ErrorCodes.ReshardCollectionAborted,
});
+awaitAbort();
// Tests that the prohibited commands succeed if the resharding operation succeeds. During the
-// operation it makes sures that the prohibited commands are rejected during the resharding
+// operation it makes sure that the prohibited commands are rejected during the resharding
// operation.
withReshardingInBackground(() => {
waitUntilReshardingInitializedOnDonor();
diff --git a/jstests/sharding/resharding_temp_ns_routing_info_unsharded.js b/jstests/sharding/resharding_temp_ns_routing_info_unsharded.js
new file mode 100644
index 00000000000..d7616222920
--- /dev/null
+++ b/jstests/sharding/resharding_temp_ns_routing_info_unsharded.js
@@ -0,0 +1,49 @@
+/**
+ * Tests that write operations on the collection being resharded succeed even when the routing
+ * information for the associated temporary resharding collection is stale.
+ */
+(function() {
+"use strict";
+
+load("jstests/libs/discover_topology.js");
+load("jstests/libs/fail_point_util.js");
+load("jstests/sharding/libs/resharding_test_fixture.js");
+
+const reshardingTest = new ReshardingTest({reshardInPlace: false});
+reshardingTest.setup();
+
+const donorShardNames = reshardingTest.donorShardNames;
+const recipientShardNames = reshardingTest.recipientShardNames;
+
+const sourceCollection = reshardingTest.createShardedCollection({
+ ns: "reshardingDb.coll",
+ shardKeyPattern: {oldKey: 1},
+ chunks: [{min: {oldKey: MinKey}, max: {oldKey: MaxKey}, shard: donorShardNames[0]}],
+ primaryShardName: recipientShardNames[0],
+});
+
+const mongos = sourceCollection.getMongo();
+const topology = DiscoverTopology.findConnectedNodes(mongos);
+const donor = new Mongo(topology.shards[donorShardNames[0]].primary);
+
+const reshardingPauseDonorBeforeCatalogCacheRefreshFailpoint =
+ configureFailPoint(donor, "reshardingPauseDonorBeforeCatalogCacheRefresh");
+
+// We trigger a refresh to make the catalog cache track the routing info for the temporary
+// resharding namespace as unsharded because the collection won't exist yet.
+assert.commandWorked(donor.adminCommand({_flushRoutingTableCacheUpdates: reshardingTest.tempNs}));
+
+reshardingTest.withReshardingInBackground( //
+ {
+ newShardKeyPattern: {newKey: 1},
+ newChunks: [{min: {newKey: MinKey}, max: {newKey: MaxKey}, shard: recipientShardNames[0]}],
+ },
+ () => {
+ reshardingPauseDonorBeforeCatalogCacheRefreshFailpoint.wait();
+
+ assert.commandWorked(sourceCollection.insert({_id: 0, oldKey: 5, newKey: 15}));
+ reshardingPauseDonorBeforeCatalogCacheRefreshFailpoint.off();
+ });
+
+reshardingTest.teardown();
+})();
diff --git a/jstests/sharding/resharding_update_tag_zones.js b/jstests/sharding/resharding_update_tag_zones.js
new file mode 100644
index 00000000000..f61fe0bb0d6
--- /dev/null
+++ b/jstests/sharding/resharding_update_tag_zones.js
@@ -0,0 +1,53 @@
+/**
+ * Testing that config.tags are correctly updated after resharding hashed shard key with zones.
+ */
+
+(function() {
+"use strict";
+
+const st = new ShardingTest({shard: 2});
+const dbName = "testDb";
+const collName = "testColl";
+const ns = dbName + "." + collName;
+
+// Enable sharding on the test DB and ensure its primary is st.shard0.shardName.
+assert.commandWorked(st.s.adminCommand({enablesharding: dbName}));
+st.ensurePrimaryShard(dbName, st.shard0.shardName);
+assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {oldKey: "hashed"}}));
+
+const existingZoneName = 'x1';
+assert.commandWorked(
+ st.s.adminCommand({addShardToZone: st.shard1.shardName, zone: existingZoneName}));
+
+assert.commandWorked(st.s.adminCommand({
+ updateZoneKeyRange: ns,
+ min: {oldKey: NumberLong("4470791281878691347")},
+ max: {oldKey: NumberLong("7766103514953448109")},
+ zone: existingZoneName
+}));
+
+assert.commandWorked(st.s.adminCommand({
+ reshardCollection: ns,
+ key: {oldKey: 1},
+ unique: false,
+ collation: {locale: 'simple'},
+ zones: [{
+ zone: existingZoneName,
+ min: {oldKey: NumberLong("4470791281878691346")},
+ max: {oldKey: NumberLong("7766103514953448108")}
+ }],
+ numInitialChunks: 2,
+}));
+
+// Find the tags docs.
+var configDB = st.s.getDB("config");
+let tags = configDB.tags.find({}).toArray();
+
+// Assert only one tag doc is present and zone ranges are correct.
+assert.eq(1, configDB.tags.countDocuments({}));
+assert.eq({oldKey: NumberLong("4470791281878691346")}, tags[0].min);
+assert.eq({oldKey: NumberLong("7766103514953448108")}, tags[0].max);
+assert.eq(existingZoneName, tags[0].tag);
+
+st.stop();
+})();
diff --git a/jstests/sharding/resharding_update_tag_zones_large.js b/jstests/sharding/resharding_update_tag_zones_large.js
new file mode 100644
index 00000000000..03f7960d711
--- /dev/null
+++ b/jstests/sharding/resharding_update_tag_zones_large.js
@@ -0,0 +1,133 @@
+/**
+ * Testing that the reshardCollection command aborts correctly when the transaction for updating
+ * the persistent state (e.g. config.collections and config.tags) in the resharding commit phase
+ * fails with a TransactionTooLargeForCache error.
+ */
+
+(function() {
+"use strict";
+
+load("jstests/libs/fail_point_util.js");
+
+function assertEqualObj(lhs, rhs, keysToIgnore) {
+ assert.eq(Object.keys(lhs).length, Object.keys(lhs).length, {lhs, rhs});
+ for (let key in rhs) {
+ if (keysToIgnore && keysToIgnore.has(key)) {
+ continue;
+ }
+
+ const value = rhs[key];
+ if (typeof value === 'object') {
+ assertEqualObj(lhs[key], rhs[key], keysToIgnore);
+ } else {
+ assert.eq(lhs[key], rhs[key], {key, actual: lhs, expected: rhs});
+ }
+ }
+}
+
+const st = new ShardingTest({
+ shard: 2,
+ configOptions:
+ {setParameter:
+ {'reshardingCriticalSectionTimeoutMillis': 24 * 60 * 60 * 1000 /* 1 day */}}
+});
+
+const configRSPrimary = st.configRS.getPrimary();
+
+const dbName = "testDb";
+const collName = "testColl";
+const ns = dbName + "." + collName;
+
+const configDB = st.s.getDB("config");
+const collectionsColl = configDB.getCollection("collections");
+const chunksColl = configDB.getCollection("chunks");
+const tagsColl = configDB.getCollection("tags");
+
+assert.commandWorked(st.s.adminCommand({enablesharding: dbName}));
+st.ensurePrimaryShard(dbName, st.shard0.shardName);
+assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {skey: "hashed"}}));
+
+const zoneName = "testZone";
+assert.commandWorked(st.s.adminCommand({addShardToZone: st.shard0.shardName, zone: zoneName}));
+
+const oldZone = {
+ tag: zoneName,
+ min: {skey: NumberLong("4470791281878691347")},
+ max: {skey: NumberLong("7766103514953448109")}
+};
+assert.commandWorked(st.s.adminCommand(
+ {updateZoneKeyRange: ns, min: oldZone.min, max: oldZone.max, zone: oldZone.tag}));
+
+const collBefore = collectionsColl.findOne({_id: ns});
+assert.neq(collBefore, null);
+const chunksBefore = chunksColl.find({uuid: collBefore.uuid}).sort({lastmod: -1}).toArray();
+assert.gte(chunksBefore.length, 1, chunksBefore);
+const tagsBefore = tagsColl.find({ns}).toArray();
+assert.gte(tagsBefore.length, 1, tagsBefore);
+
+const reshardingFunc = (mongosHost, ns, zoneName) => {
+ const mongos = new Mongo(mongosHost);
+ const newZone = {
+ tag: zoneName,
+ min: {skey: NumberLong("4470791281878691346")},
+ max: {skey: NumberLong("7766103514953448108")}
+ };
+ jsTest.log("Start resharding");
+ const reshardingRes = mongos.adminCommand({
+ reshardCollection: ns,
+ key: {skey: 1},
+ unique: false,
+ collation: {locale: 'simple'},
+ zones: [{zone: newZone.tag, min: newZone.min, max: newZone.max}],
+ numInitialChunks: 2,
+ });
+ jsTest.log("Finished resharding");
+ return reshardingRes;
+};
+let reshardingThread = new Thread(reshardingFunc, st.s.host, ns, zoneName);
+
+const persistFp =
+ configureFailPoint(configRSPrimary, "reshardingPauseCoordinatorBeforeDecisionPersisted");
+reshardingThread.start();
+persistFp.wait();
+
+const commitFp = configureFailPoint(configRSPrimary,
+ "failCommand",
+ {
+ failCommands: ["commitTransaction"],
+ failInternalCommands: true,
+ failLocalClients: true,
+ errorCode: ErrorCodes.TransactionTooLargeForCache,
+ },
+ {times: 1});
+persistFp.off();
+commitFp.wait();
+commitFp.off();
+const reshardingRes = reshardingThread.returnData();
+
+assert.commandFailedWithCode(reshardingRes, ErrorCodes.TransactionTooLargeForCache);
+
+const collAfter = collectionsColl.findOne({_id: ns});
+assert.neq(collAfter, null);
+const chunksAfter = chunksColl.find({uuid: collAfter.uuid}).sort({lastmod: -1}).toArray();
+const tagsAfter = tagsColl.find({ns}).toArray();
+
+jsTest.log(
+ "Verify that the collection metadata remains the same since the resharding operation failed.");
+
+assertEqualObj(collBefore, collAfter);
+
+assert.eq(chunksBefore.length, chunksAfter.length, {chunksBefore, chunksAfter});
+for (let i = 0; i < chunksAfter.length; i++) {
+ // Ignore "lastmod" when verifying the newest chunk because resharding bumps the minor version
+ // of the newest chunk whenever it goes through a state transition.
+ assertEqualObj(chunksBefore[i], chunksAfter[i], new Set(i == 0 ? ["lastmod"] : []));
+}
+
+assert.eq(tagsBefore.length, tagsAfter.length, {tagsBefore, tagsAfter});
+for (let i = 0; i < tagsAfter.length; i++) {
+ assertEqualObj(tagsBefore[i], tagsAfter[i]);
+}
+
+st.stop();
+})();
diff --git a/jstests/sharding/run_restore_unsharded.js b/jstests/sharding/run_restore_unsharded.js
new file mode 100644
index 00000000000..038e79f1dd9
--- /dev/null
+++ b/jstests/sharding/run_restore_unsharded.js
@@ -0,0 +1,67 @@
+/**
+ * Tests that the "_configsvrRunRestore" command restores databases with unsharded collections
+ * referenced in the "local.system.collections_to_restore" collection.
+ *
+ * @tags: [
+ * requires_persistence,
+ * ]
+ */
+(function() {
+"use strict";
+
+load("jstests/libs/feature_flag_util.js");
+
+const s =
+ new ShardingTest({name: "runRestore", shards: 2, mongos: 1, config: 1, other: {chunkSize: 1}});
+
+let mongos = s.s0;
+let db = s.getDB("test");
+if (!FeatureFlagUtil.isEnabled(s.configRS.getPrimary().getDB("test"), "SelectiveBackup")) {
+ jsTestLog("Skipping as featureFlagSelectiveBackup is not enabled");
+ s.stop();
+ return;
+}
+
+s.adminCommand({enablesharding: "test"});
+s.ensurePrimaryShard("test", s.shard0.shardName);
+
+// Create an unsharded collection.
+assert.commandWorked(db.createCollection("a"));
+const collUUID =
+ s.shard0.getDB("test").runCommand({listCollections: 1}).cursor.firstBatch[0].info.uuid;
+
+// Only sharded collections appear in config.collections
+assert.eq(0, mongos.getDB("config").getCollection("collections").find({_id: "test.a"}).count());
+
+assert.eq(1, mongos.getDB("config").getCollection("locks").find({_id: "test"}).count());
+assert.eq(1, mongos.getDB("config").getCollection("databases").find({_id: "test"}).count());
+
+s.stop({noCleanData: true});
+
+const configDbPath = s.c0.dbpath;
+
+// Start the config server in standalone restore mode.
+let conn = MongoRunner.runMongod({noCleanData: true, dbpath: configDbPath, restore: ""});
+assert(conn);
+
+assert.commandWorked(conn.getDB("admin").runCommand({setParameter: 1, logLevel: 1}));
+
+// Create the "local.system.collections_to_restore" collection and insert "test.a".
+assert.commandWorked(conn.getDB("local").createCollection("system.collections_to_restore"));
+assert.commandWorked(conn.getDB("local").getCollection("system.collections_to_restore").insert({
+ ns: "test.a",
+ uuid: collUUID
+}));
+
+assert.commandWorked(conn.getDB("admin").runCommand({_configsvrRunRestore: 1}));
+
+// Only sharded collections appear in config.collections
+assert.eq(0, conn.getDB("config").getCollection("collections").find({_id: "test.a"}).count());
+
+let locks = conn.getDB("config").getCollection("locks").find({_id: "test"}).toArray();
+assert.eq(1, locks.length);
+assert.eq(0, locks[0].state); // State::UNLOCKED
+assert.eq(1, conn.getDB("config").getCollection("databases").find({_id: "test"}).count());
+
+MongoRunner.stopMongod(conn);
+}());
diff --git a/jstests/sharding/safe_secondary_reads_drop_recreate.js b/jstests/sharding/safe_secondary_reads_drop_recreate.js
index d6ad842361f..6ace4437983 100644
--- a/jstests/sharding/safe_secondary_reads_drop_recreate.js
+++ b/jstests/sharding/safe_secondary_reads_drop_recreate.js
@@ -72,6 +72,7 @@ let testCases = {
_configsvrShardCollection:
{skip: "primary only"}, // TODO SERVER-58843: Remove once 6.0 becomes last LTS
_configsvrUpdateZoneKeyRange: {skip: "primary only"},
+ _dropConnectionsToMongot: {skip: "internal command"},
_flushReshardingStateChange: {skip: "does not return user data"},
_flushRoutingTableCacheUpdates: {skip: "does not return user data"},
_flushRoutingTableCacheUpdatesWithWriteConcern: {skip: "does not return user data"},
@@ -82,6 +83,7 @@ let testCases = {
_killOperations: {skip: "does not return user data"},
_mergeAuthzCollections: {skip: "primary only"},
_migrateClone: {skip: "primary only"},
+ _mongotConnPoolStats: {skip: "internal command"},
_shardsvrCompactStructuredEncryptionData: {skip: "primary only"},
_shardsvrMovePrimary: {skip: "primary only"},
_shardsvrMoveRange: {skip: "primary only"},
@@ -162,6 +164,7 @@ let testCases = {
create: {skip: "primary only"},
createIndexes: {skip: "primary only"},
createRole: {skip: "primary only"},
+ createSearchIndexes: {skip: "primary only"},
createUser: {skip: "primary only"},
currentOp: {skip: "does not return user data"},
dataSize: {skip: "does not return user data"},
@@ -188,6 +191,7 @@ let testCases = {
dropDatabase: {skip: "primary only"},
dropIndexes: {skip: "primary only"},
dropRole: {skip: "primary only"},
+ dropSearchIndex: {skip: "primary only"},
dropUser: {skip: "primary only"},
echo: {skip: "does not return user data"},
emptycapped: {skip: "primary only"},
@@ -246,6 +250,7 @@ let testCases = {
listCommands: {skip: "does not return user data"},
listDatabases: {skip: "primary only"},
listIndexes: {skip: "primary only"},
+ listSearchIndexes: {skip: "primary only"},
listShards: {skip: "does not return user data"},
lockInfo: {skip: "primary only"},
logApplicationMessage: {skip: "primary only"},
@@ -329,7 +334,7 @@ let testCases = {
setDefaultRWConcern: {skip: "primary only"},
setIndexCommitQuorum: {skip: "primary only"},
setFeatureCompatibilityVersion: {skip: "primary only"},
- setFreeMonitoring: {skip: "primary only"},
+ setProfilingFilterGlobally: {skip: "does not return user data"},
setParameter: {skip: "does not return user data"},
setShardVersion: {skip: "does not return user data"},
setClusterParameter: {skip: "does not return user data"},
@@ -354,6 +359,7 @@ let testCases = {
top: {skip: "does not return user data"},
update: {skip: "primary only"},
updateRole: {skip: "primary only"},
+ updateSearchIndex: {skip: "primary only"},
updateUser: {skip: "primary only"},
updateZoneKeyRange: {skip: "primary only"},
usersInfo: {skip: "primary only"},
diff --git a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
index 3e01be669a4..27c3ca2cb0e 100644
--- a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
+++ b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
@@ -82,6 +82,7 @@ let testCases = {
_configsvrShardCollection:
{skip: "primary only"}, // TODO SERVER-58843: Remove once 6.0 becomes last LTS
_configsvrUpdateZoneKeyRange: {skip: "primary only"},
+ _dropConnectionsToMongot: {skip: "internal command"},
_flushReshardingStateChange: {skip: "does not return user data"},
_flushRoutingTableCacheUpdates: {skip: "does not return user data"},
_flushRoutingTableCacheUpdatesWithWriteConcern: {skip: "does not return user data"},
@@ -92,6 +93,7 @@ let testCases = {
_killOperations: {skip: "does not return user data"},
_mergeAuthzCollections: {skip: "primary only"},
_migrateClone: {skip: "primary only"},
+ _mongotConnPoolStats: {skip: "internal command"},
_shardsvrCompactStructuredEncryptionData: {skip: "primary only"},
_shardsvrMovePrimary: {skip: "primary only"},
_shardsvrMoveRange: {skip: "primary only"},
@@ -184,6 +186,7 @@ let testCases = {
create: {skip: "primary only"},
createIndexes: {skip: "primary only"},
createRole: {skip: "primary only"},
+ createSearchIndexes: {skip: "does not return user data"},
createUser: {skip: "primary only"},
currentOp: {skip: "does not return user data"},
dataSize: {skip: "does not return user data"},
@@ -215,6 +218,7 @@ let testCases = {
dropDatabase: {skip: "primary only"},
dropIndexes: {skip: "primary only"},
dropRole: {skip: "primary only"},
+ dropSearchIndex: {skip: "does not return user data"},
dropUser: {skip: "primary only"},
echo: {skip: "does not return user data"},
emptycapped: {skip: "primary only"},
@@ -279,6 +283,7 @@ let testCases = {
listCommands: {skip: "does not return user data"},
listDatabases: {skip: "primary only"},
listIndexes: {skip: "primary only"},
+ listSearchIndexes: {skip: "does not return user data"},
listShards: {skip: "does not return user data"},
lockInfo: {skip: "primary only"},
logApplicationMessage: {skip: "primary only"},
@@ -400,7 +405,7 @@ let testCases = {
setDefaultRWConcern: {skip: "primary only"},
setIndexCommitQuorum: {skip: "primary only"},
setFeatureCompatibilityVersion: {skip: "primary only"},
- setFreeMonitoring: {skip: "primary only"},
+ setProfilingFilterGlobally: {skip: "does not return user data"},
setParameter: {skip: "does not return user data"},
setShardVersion: {skip: "does not return user data"},
setClusterParameter: {skip: "does not return user data"},
@@ -425,6 +430,7 @@ let testCases = {
top: {skip: "does not return user data"},
update: {skip: "primary only"},
updateRole: {skip: "primary only"},
+ updateSearchIndex: {skip: "does not return user data"},
updateUser: {skip: "primary only"},
updateZoneKeyRange: {skip: "primary only"},
usersInfo: {skip: "primary only"},
diff --git a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
index 64ea7271f2e..1199327750f 100644
--- a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
+++ b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
@@ -74,6 +74,7 @@ let testCases = {
_configsvrShardCollection:
{skip: "primary only"}, // TODO SERVER-58843: Remove once 6.0 becomes last LTS
_configsvrUpdateZoneKeyRange: {skip: "primary only"},
+ _dropConnectionsToMongot: {skip: "does not return user data"},
_flushReshardingStateChange: {skip: "does not return user data"},
_flushRoutingTableCacheUpdates: {skip: "does not return user data"},
_flushRoutingTableCacheUpdatesWithWriteConcern: {skip: "does not return user data"},
@@ -84,6 +85,7 @@ let testCases = {
_killOperations: {skip: "does not return user data"},
_mergeAuthzCollections: {skip: "primary only"},
_migrateClone: {skip: "primary only"},
+ _mongotConnPoolStats: {skip: "internal command"},
_shardsvrCompactStructuredEncryptionData: {skip: "primary only"},
_shardsvrMovePrimary: {skip: "primary only"},
_shardsvrMoveRange: {skip: "primary only"},
@@ -166,6 +168,7 @@ let testCases = {
create: {skip: "primary only"},
createIndexes: {skip: "primary only"},
createRole: {skip: "primary only"},
+ createSearchIndexes: {skip: "does not return user data"},
createUser: {skip: "primary only"},
currentOp: {skip: "does not return user data"},
dataSize: {skip: "does not return user data"},
@@ -192,6 +195,7 @@ let testCases = {
dropDatabase: {skip: "primary only"},
dropIndexes: {skip: "primary only"},
dropRole: {skip: "primary only"},
+ dropSearchIndex: {skip: "does not return user data"},
dropUser: {skip: "primary only"},
echo: {skip: "does not return user data"},
emptycapped: {skip: "primary only"},
@@ -251,6 +255,7 @@ let testCases = {
listCommands: {skip: "does not return user data"},
listDatabases: {skip: "primary only"},
listIndexes: {skip: "primary only"},
+ listSearchIndexes: {skip: "does not return user data"},
listShards: {skip: "does not return user data"},
lockInfo: {skip: "primary only"},
logApplicationMessage: {skip: "primary only"},
@@ -336,7 +341,7 @@ let testCases = {
setDefaultRWConcern: {skip: "primary only"},
setIndexCommitQuorum: {skip: "primary only"},
setFeatureCompatibilityVersion: {skip: "primary only"},
- setFreeMonitoring: {skip: "primary only"},
+ setProfilingFilterGlobally: {skip: "does not return user data"},
setParameter: {skip: "does not return user data"},
setShardVersion: {skip: "does not return user data"},
setClusterParameter: {skip: "does not return user data"},
@@ -361,6 +366,7 @@ let testCases = {
top: {skip: "does not return user data"},
update: {skip: "primary only"},
updateRole: {skip: "primary only"},
+ updateSearchIndex: {skip: "does not return user data"},
updateUser: {skip: "primary only"},
updateZoneKeyRange: {skip: "primary only"},
usersInfo: {skip: "primary only"},
diff --git a/jstests/sharding/server_status_crud_metrics.js b/jstests/sharding/server_status_crud_metrics.js
index 4f83d7e4b6e..99821f78b4c 100644
--- a/jstests/sharding/server_status_crud_metrics.js
+++ b/jstests/sharding/server_status_crud_metrics.js
@@ -26,15 +26,14 @@ assert.commandWorked(unshardedColl.insert({x: 1, _id: 1}));
// Verification for 'updateOneOpStyleBroadcastWithExactIDCount' metric.
// Should increment the metric as the update cannot target single shard and are {multi:false}.
-assert.commandWorked(testDB.coll.update({_id: "missing"}, {$set: {a: 1}}, {multi: false}));
-assert.commandWorked(testDB.coll.update({_id: 1}, {$set: {a: 2}}, {multi: false}));
+assert.commandWorked(testColl.update({_id: "missing"}, {$set: {a: 1}}, {multi: false}));
+assert.commandWorked(testColl.update({_id: 1}, {$set: {a: 2}}, {multi: false}));
// Should increment the metric because we broadcast by _id, even though the update subsequently
// fails on the individual shard.
-assert.commandFailedWithCode(testDB.coll.update({_id: 1}, {$set: {x: 2}}, {multi: false}), 31025);
-assert.commandFailedWithCode(
- testDB.coll.update({_id: 1}, {$set: {x: 12}, $hello: 1}, {multi: false}),
- ErrorCodes.FailedToParse);
+assert.commandFailedWithCode(testColl.update({_id: 1}, {$set: {x: 2}}, {multi: false}), 31025);
+assert.commandFailedWithCode(testColl.update({_id: 1}, {$set: {x: 12}, $hello: 1}, {multi: false}),
+ ErrorCodes.FailedToParse);
let mongosServerStatus = testDB.adminCommand({serverStatus: 1});
@@ -42,21 +41,21 @@ let mongosServerStatus = testDB.adminCommand({serverStatus: 1});
assert.eq(4, mongosServerStatus.metrics.query.updateOneOpStyleBroadcastWithExactIDCount);
// Shouldn't increment the metric when {multi:true}.
-assert.commandWorked(testDB.coll.update({_id: 1}, {$set: {a: 3}}, {multi: true}));
-assert.commandWorked(testDB.coll.update({}, {$set: {a: 3}}, {multi: true}));
+assert.commandWorked(testColl.update({_id: 1}, {$set: {a: 3}}, {multi: true}));
+assert.commandWorked(testColl.update({}, {$set: {a: 3}}, {multi: true}));
// Shouldn't increment the metric when update can target single shard.
-assert.commandWorked(testDB.coll.update({x: 11}, {$set: {a: 2}}, {multi: false}));
-assert.commandWorked(testDB.coll.update({x: 1}, {$set: {a: 2}}, {multi: false}));
+assert.commandWorked(testColl.update({x: 11}, {$set: {a: 2}}, {multi: false}));
+assert.commandWorked(testColl.update({x: 1}, {$set: {a: 2}}, {multi: false}));
// Shouldn't increment the metric for replacement style updates.
-assert.commandWorked(testDB.coll.update({_id: 1}, {x: 1, a: 2}));
-assert.commandWorked(testDB.coll.update({x: 1}, {x: 1, a: 1}));
+assert.commandWorked(testColl.update({_id: 1}, {x: 1, a: 2}));
+assert.commandWorked(testColl.update({x: 1}, {x: 1, a: 1}));
// Shouldn't increment the metric when routing fails.
-assert.commandFailedWithCode(testDB.coll.update({}, {$set: {x: 2}}, {multi: false}),
+assert.commandFailedWithCode(testColl.update({}, {$set: {x: 2}}, {multi: false}),
ErrorCodes.InvalidOptions);
-assert.commandFailedWithCode(testDB.coll.update({_id: 1}, {$set: {x: 2}}, {upsert: true}),
+assert.commandFailedWithCode(testColl.update({_id: 1}, {$set: {x: 2}}, {upsert: true}),
ErrorCodes.ShardKeyNotFound);
// Shouldn't increment the metrics for unsharded collection.
@@ -65,7 +64,7 @@ assert.commandWorked(unshardedColl.update({_id: 1}, {$set: {a: 2}}, {multi: fals
// Shouldn't incement the metrics when query had invalid operator.
assert.commandFailedWithCode(
- testDB.coll.update({_id: 1, $invalidOperator: 1}, {$set: {a: 2}}, {multi: false}),
+ testColl.update({_id: 1, $invalidOperator: 1}, {$set: {a: 2}}, {multi: false}),
ErrorCodes.BadValue);
mongosServerStatus = testDB.adminCommand({serverStatus: 1});
diff --git a/jstests/sharding/shard_collection_basic.js b/jstests/sharding/shard_collection_basic.js
index 7fc136dbc8b..9c22eff25b0 100644
--- a/jstests/sharding/shard_collection_basic.js
+++ b/jstests/sharding/shard_collection_basic.js
@@ -1,3 +1,7 @@
+/**
+ * Disabled in multiversion, see SERVER-69290 for more details.
+ * @tags: [multiversion_incompatible]
+ */
(function() {
'use strict';
diff --git a/jstests/sharding/shard_drain_works_with_chunks_of_any_size.js b/jstests/sharding/shard_drain_works_with_chunks_of_any_size.js
new file mode 100644
index 00000000000..b9f8aff9460
--- /dev/null
+++ b/jstests/sharding/shard_drain_works_with_chunks_of_any_size.js
@@ -0,0 +1,92 @@
+/*
+ * Shard a collection with documents spread on 2 shards and then call `removeShard` checking that:
+ * - Huge non-jumbo chunks are split during draining (moveRange moves off pieces of `chunkSize` MB)
+ * - Jumbo chunks are moved off (without splitting, since it's not possible)
+ *
+ * Regression test for SERVER-76550.
+ *
+ * @tags: [ requires_fcv_60 ]
+ */
+
+(function() {
+"use strict";
+load("jstests/sharding/libs/find_chunks_util.js");
+
+function removeShard(st, shardName, timeout) {
+ if (timeout == undefined) {
+ timeout = 5 * 60 * 1000; // 5 minutes
+ }
+
+ assert.soon(function() {
+ const res = st.s.adminCommand({removeShard: shardName});
+ if (!res.ok && res.code === ErrorCodes.ShardNotFound) {
+ // If the config server primary steps down right after removing the config.shards doc
+ // for the shard but before responding with "state": "completed", the mongos would retry
+ // the _configsvrRemoveShard command against the new config server primary, which would
+ // not find the removed shard in its ShardRegistry if it has done a ShardRegistry reload
+ // after the config.shards doc for the shard was removed. This would cause the command
+ // to fail with ShardNotFound.
+ return true;
+ }
+ assert.commandWorked(res);
+ return res.state == 'completed';
+ }, "failed to remove shard " + shardName + " within " + timeout + "ms", timeout);
+}
+
+const st = new ShardingTest({other: {enableBalancer: false, chunkSize: 1}});
+const mongos = st.s0;
+const configDB = st.getDB('config');
+
+st.forEachConfigServer((conn) => {
+ assert.commandWorked(conn.adminCommand({
+ configureFailPoint: 'overrideBalanceRoundInterval',
+ mode: 'alwaysOn',
+ data: {intervalMs: 100}
+ }));
+});
+
+const dbName = 'test';
+const collName = 'collToDrain';
+const ns = dbName + '.' + collName;
+const db = st.getDB(dbName);
+const coll = db.getCollection(collName);
+
+// Shard collection with shard0 as db primary
+assert.commandWorked(
+ mongos.adminCommand({enablesharding: dbName, primaryShard: st.shard0.shardName}));
+assert.commandWorked(mongos.adminCommand({shardCollection: ns, key: {x: 1}}));
+
+// shard0 owns docs with shard key [MinKey, 0), shard1 owns docs with shard key [0, MaxKey)
+assert.commandWorked(st.s.adminCommand(
+ {moveRange: ns, min: {x: 0}, max: {x: MaxKey}, toShard: st.shard1.shardName}));
+
+// Insert ~20MB of docs with different shard keys (10MB on shard0 and 10MB on shard1)
+// and ~10MB of docs with the same shard key (jumbo chunk)
+const big = 'X'.repeat(1024 * 1024); // 1MB
+const jumboKey = 100;
+var bulk = coll.initializeUnorderedBulkOp();
+for (var i = -10; i < 10; i++) {
+ bulk.insert({x: i, big: big});
+ bulk.insert({x: jumboKey, big: big});
+}
+assert.commandWorked(bulk.execute());
+
+// Check that there are only 2 big chunks before starting draining
+const chunksBeforeDrain = findChunksUtil.findChunksByNs(configDB, ns).toArray();
+assert.eq(2, chunksBeforeDrain.length);
+
+st.startBalancer();
+
+// Remove shard 1 and wait for all chunks to be moved off from it
+removeShard(st, st.shard1.shardName);
+
+// Check that after draining there are 12 chunks on shard0:
+// - [MinKey, 0) original chunk on shard 1
+// - [0, 1), [1, 2), ... [8, 9) 1 MB chunks
+// - [9, MaxKey) 10MB jumbo chunk
+const chunksAfterDrain =
+ findChunksUtil.findChunksByNs(configDB, ns, {shard: st.shard0.shardName}).toArray();
+assert.eq(12, chunksAfterDrain.length);
+
+st.stop();
+})();
diff --git a/jstests/sharding/shard_existing.js b/jstests/sharding/shard_existing.js
deleted file mode 100644
index 5430b5577fc..00000000000
--- a/jstests/sharding/shard_existing.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * @tags: [
- * requires_fcv_51,
- * ]
- */
-
-(function() {
-'use strict';
-
-load("jstests/sharding/libs/find_chunks_util.js");
-
-var s = new ShardingTest({name: "shard_existing", shards: 2, mongos: 1, other: {chunkSize: 1}});
-var db = s.getDB("test");
-
-var stringSize = 10000;
-var numDocs = 2000;
-
-// we want a lot of data, so lets make a string to cheat :)
-var bigString = new Array(stringSize).toString();
-var docSize = Object.bsonsize({_id: numDocs, s: bigString});
-var totalSize = docSize * numDocs;
-print("NumDocs: " + numDocs + " DocSize: " + docSize + " TotalSize: " + totalSize);
-
-var bulk = db.data.initializeUnorderedBulkOp();
-for (var i = 0; i < numDocs; i++) {
- bulk.insert({_id: i, s: bigString});
-}
-assert.commandWorked(bulk.execute());
-
-var avgObjSize = db.data.stats().avgObjSize;
-var dataSize = db.data.stats().size;
-assert.lte(totalSize, dataSize);
-
-s.adminCommand({enablesharding: "test"});
-s.ensurePrimaryShard('test', s.shard1.shardName);
-var res = s.adminCommand({shardcollection: "test.data", key: {_id: 1}});
-printjson(res);
-
-// number of chunks should be approx equal to the total data size / chunk size
-var numChunks = findChunksUtil.findChunksByNs(s.config, 'test.data').itcount();
-var guess = Math.ceil(dataSize / (1024 * 1024 + avgObjSize));
-assert.lte(Math.abs(numChunks - guess), 2, "not right number of chunks");
-
-s.stop();
-})();
diff --git a/jstests/sharding/shard_existing_coll_chunk_count.js b/jstests/sharding/shard_existing_coll_chunk_count.js
deleted file mode 100644
index c53dca8fdf9..00000000000
--- a/jstests/sharding/shard_existing_coll_chunk_count.js
+++ /dev/null
@@ -1,177 +0,0 @@
-/**
- * This test confirms that after sharding a collection with some pre-existing data,
- * the resulting chunks aren't auto-split too aggressively.
- *
- * @tags: [
- * requires_fcv_51,
- * requires_persistence
- * ]
- */
-(function() {
-'use strict';
-load('jstests/sharding/autosplit_include.js');
-load("jstests/sharding/libs/find_chunks_util.js");
-
-var s = new ShardingTest({
- name: "shard_existing_coll_chunk_count",
- shards: 1,
- mongos: 1,
- other: {enableAutoSplit: true},
-});
-
-assert.commandWorked(s.s.adminCommand({enablesharding: "test"}));
-
-var collNum = 0;
-var overhead = Object.bsonsize({_id: ObjectId(), i: 1, pad: ""});
-
-var getNumberChunks = function(ns) {
- return findChunksUtil.countChunksForNs(s.getDB("config"), ns);
-};
-
-var runCase = function(opts) {
- // Expected options.
- assert.gte(opts.docSize, 0);
- assert.gte(opts.stages.length, 2);
-
- // Compute padding.
- if (opts.docSize < overhead) {
- var pad = "";
- } else {
- var pad = (new Array(opts.docSize - overhead + 1)).join(' ');
- }
-
- collNum++;
- var db = s.getDB("test");
- var collName = "coll" + collNum;
- var coll = db.getCollection(collName);
- var i = 0;
- var limit = 0;
- var stageNum = 0;
- var stage = opts.stages[stageNum];
-
- // Insert initial docs.
- var bulk = coll.initializeUnorderedBulkOp();
- limit += stage.numDocsToInsert;
- for (; i < limit; i++) {
- bulk.insert({i, pad});
- }
- assert.commandWorked(bulk.execute());
-
- // Create shard key index.
- assert.commandWorked(coll.createIndex({i: 1}));
-
- // Shard collection.
- assert.commandWorked(s.s.adminCommand({shardcollection: coll.getFullName(), key: {i: 1}}));
-
- // Confirm initial number of chunks.
- var numChunks = getNumberChunks(coll.getFullName());
- assert.eq(numChunks,
- stage.expectedNumChunks,
- 'in ' + coll.getFullName() + ' expected ' + stage.expectedNumChunks +
- ' initial chunks, but found ' + numChunks + '\nopts: ' + tojson(opts) +
- '\nchunks:\n' + s.getChunksString(coll.getFullName()));
-
- // Do the rest of the stages.
- for (stageNum = 1; stageNum < opts.stages.length; stageNum++) {
- stage = opts.stages[stageNum];
-
- // Insert the later docs (one at a time, to maximise the autosplit effects).
- limit += stage.numDocsToInsert;
- for (; i < limit; i++) {
- coll.insert({i, pad});
-
- waitForOngoingChunkSplits(s);
- }
-
- // Confirm number of chunks for this stage.
- var numChunks = getNumberChunks(coll.getFullName());
- assert.lte(numChunks,
- stage.expectedNumChunks,
- 'in ' + coll.getFullName() + ' expected ' + stage.expectedNumChunks +
- ' chunks for stage ' + stageNum + ', but found ' + numChunks + '\nopts: ' +
- tojson(opts) + '\nchunks:\n' + s.getChunksString(coll.getFullName()));
- }
-};
-
-// Original problematic case.
-runCase({
- docSize: 0,
- stages: [
- {numDocsToInsert: 20000, expectedNumChunks: 1},
- {numDocsToInsert: 7, expectedNumChunks: 1},
- {numDocsToInsert: 1000, expectedNumChunks: 1},
- ],
-});
-
-// Original problematic case (worse).
-runCase({
- docSize: 0,
- stages: [
- {numDocsToInsert: 90000, expectedNumChunks: 1},
- {numDocsToInsert: 7, expectedNumChunks: 1},
- {numDocsToInsert: 1000, expectedNumChunks: 1},
- ],
-});
-
-// Pathological case #1.
-runCase({
- docSize: 522,
- stages: [
- {numDocsToInsert: 8191, expectedNumChunks: 1},
- {numDocsToInsert: 2, expectedNumChunks: 1},
- {numDocsToInsert: 1000, expectedNumChunks: 1},
- ],
-});
-
-// Pathological case #2.
-runCase({
- docSize: 522,
- stages: [
- {numDocsToInsert: 8192, expectedNumChunks: 1},
- {numDocsToInsert: 8192, expectedNumChunks: 1},
- ],
-});
-
-// Lower chunksize to 1MB, and restart the mongod for it to take. We also
-// need to restart mongos for the case of the last-lts suite where the
-// shard is also last-lts.
-assert.commandWorked(
- s.getDB("config").getCollection("settings").update({_id: "chunksize"}, {$set: {value: 1}}, {
- upsert: true
- }));
-
-s.restartMongos(0);
-s.restartShardRS(0);
-
-// Original problematic case, scaled down to smaller chunksize.
-runCase({
- docSize: 0,
- stages: [
- {numDocsToInsert: 10000, expectedNumChunks: 1},
- {numDocsToInsert: 10, expectedNumChunks: 1},
- {numDocsToInsert: 20, expectedNumChunks: 1},
- {numDocsToInsert: 40, expectedNumChunks: 1},
- {numDocsToInsert: 1000, expectedNumChunks: 1},
- ],
-});
-
-// Docs just smaller than half chunk size.
-runCase({
- docSize: 510 * 1024,
- stages: [
- {numDocsToInsert: 10, expectedNumChunks: 6},
- {numDocsToInsert: 10, expectedNumChunks: 12},
- ],
-});
-
-// Docs just larger than half chunk size.
-runCase({
- docSize: 514 * 1024,
- stages: [
- {numDocsToInsert: 10, expectedNumChunks: 10},
- {numDocsToInsert: 10, expectedNumChunks: 20},
- ],
-});
-
-s.stop();
-})();
diff --git a/jstests/sharding/shard_keys_with_dollar_sign.js b/jstests/sharding/shard_keys_with_dollar_sign.js
new file mode 100644
index 00000000000..c7fbbc73be5
--- /dev/null
+++ b/jstests/sharding/shard_keys_with_dollar_sign.js
@@ -0,0 +1,75 @@
+/**
+ * Tests that the shardCollection command and reshardCollection command correctly reject a shard key
+ * that has a field name that starts with '$' or contains parts that start with '$' unless the part
+ * is a DBRef (i.e. is equal to '$id', '$db' or '$ref').
+ */
+(function() {
+"use strict";
+
+const criticalSectionTimeoutMS = 24 * 60 * 60 * 1000; // 1 day
+const st = new ShardingTest({
+ shards: 1,
+ other: {
+ // Avoid spurious failures with small 'ReshardingCriticalSectionTimeout' values being set.
+ configOptions:
+ {setParameter: {reshardingCriticalSectionTimeoutMillis: criticalSectionTimeoutMS}}
+ }
+});
+
+const dbName = "testDb";
+const ns0 = dbName + ".testColl0";
+const ns1 = dbName + ".testColl1";
+const ns2 = dbName + ".testColl2";
+const db = st.s.getDB(dbName);
+
+function testValidation(key, {isValidIndexKey, isValidShardKey}) {
+ jsTest.log(`Testing ${tojson({key, isValidIndexKey, isValidShardKey})}`);
+ assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
+ st.ensurePrimaryShard(dbName, st.shard0.name);
+
+ const createIndexRes = db.getCollection(ns0).createIndex(key);
+ if (isValidIndexKey) {
+ assert.commandWorked(createIndexRes);
+ } else {
+ assert.commandFailedWithCode(createIndexRes, ErrorCodes.CannotCreateIndex);
+ }
+
+ const shardCollectionRes = st.s.adminCommand({shardCollection: ns1, key});
+ if (isValidShardKey) {
+ assert.commandWorked(shardCollectionRes);
+ } else {
+ assert.commandFailedWithCode(shardCollectionRes, ErrorCodes.BadValue);
+ }
+
+ assert.commandWorked(st.s.adminCommand({shardCollection: ns2, key: {_id: 1}}));
+ const reshardCollectionRes = st.s.adminCommand({reshardCollection: ns2, key});
+ if (isValidShardKey) {
+ assert.commandWorked(reshardCollectionRes);
+ } else {
+ assert.commandFailedWithCode(reshardCollectionRes, ErrorCodes.BadValue);
+ }
+
+ assert.commandWorked(db.dropDatabase());
+}
+
+testValidation({"$x": 1}, {isValidIndexKey: false, isValidShardKey: false});
+testValidation({"x.$y": 1}, {isValidIndexKey: false, isValidShardKey: false});
+testValidation({"$**": 1}, {isValidIndexKey: true, isValidShardKey: false});
+testValidation({"x.$**": 1}, {isValidIndexKey: true, isValidShardKey: false});
+testValidation({"$": 1}, {isValidIndexKey: false, isValidShardKey: false});
+
+testValidation({"x$": 1}, {isValidIndexKey: true, isValidShardKey: true});
+testValidation({"x$.y": 1}, {isValidIndexKey: true, isValidShardKey: true});
+testValidation({"x.y$": 1}, {isValidIndexKey: true, isValidShardKey: true});
+
+// Verify that a shard key can have a field that contains a DBRef as long as the field itself
+// does not start with '$'.
+testValidation({"$id": 1}, {isValidIndexKey: false, isValidShardKey: false});
+testValidation({"$db": 1}, {isValidIndexKey: false, isValidShardKey: false});
+testValidation({"$ref": 1}, {isValidIndexKey: false, isValidShardKey: false});
+testValidation({"x.$id": 1}, {isValidIndexKey: true, isValidShardKey: true});
+testValidation({"x.$db": 1}, {isValidIndexKey: true, isValidShardKey: true});
+testValidation({"x.$ref": 1}, {isValidIndexKey: true, isValidShardKey: true});
+
+st.stop();
+})();
diff --git a/jstests/sharding/sharded_data_distribution.js b/jstests/sharding/sharded_data_distribution.js
new file mode 100644
index 00000000000..8e12f291c65
--- /dev/null
+++ b/jstests/sharding/sharded_data_distribution.js
@@ -0,0 +1,188 @@
+/*
+ * Test to validate the $shardedDataDistribution stage.
+ *
+ * @tags: [
+ * requires_fcv_60,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+function testShardedDataAggregationStage() {
+ // Get all expected results in obj format
+ const fooResults = fooColl.aggregate([{$collStats: {storageStats: {}}}]).toArray();
+ assert.neq(null, fooResults);
+ const bazResults = bazColl.aggregate([{$collStats: {storageStats: {}}}]).toArray();
+ assert.neq(null, bazResults);
+
+ const objFooResults = {};
+ for (let fooRes of fooResults) {
+ objFooResults[fooRes.shard] = fooRes;
+ }
+
+ const objBazResults = {};
+ for (const bazRes of bazResults) {
+ objBazResults[bazRes.shard] = bazRes;
+ }
+
+ const expectedResults = {[ns1]: objFooResults, [ns2]: objBazResults};
+
+ // Get data to validate
+ const outputData = adminDb.aggregate([{$shardedDataDistribution: {}}]).toArray();
+
+ assert.gte(outputData.length, 2);
+
+ // Test the data obtained by $shardedDataDistribution stage
+ for (const data of outputData) {
+ const ns = data.ns;
+
+ // Check only for namespaces test.foo and bar.baz
+ if (expectedResults.hasOwnProperty(ns)) {
+ // Check for length
+ assert.eq(data.shards.length, Object.keys(expectedResults[ns]).length);
+
+ // Check for data
+ for (const shard of data.shards) {
+ const outputShardName = shard.shardName;
+ const outputOwnedSizeBytes = shard.ownedSizeBytes;
+ const outputOrphanedSizeBytes = shard.orphanedSizeBytes;
+ const outputNumOwnedDocuments = shard.numOwnedDocuments;
+ const outputNumOrphanedDocs = shard.numOrphanedDocs;
+
+ assert.eq(true, expectedResults[ns].hasOwnProperty(outputShardName));
+
+ const avgObjSize = expectedResults[ns][outputShardName].storageStats.avgObjSize;
+ const numOrphanDocs =
+ expectedResults[ns][outputShardName].storageStats.numOrphanDocs;
+ const storageStatsCount = expectedResults[ns][outputShardName].storageStats.count;
+
+ const expectedOwnedSizeBytes = (storageStatsCount - numOrphanDocs) * avgObjSize;
+ const expectedOrphanedSizeBytes = numOrphanDocs * avgObjSize;
+ const expectedNumOwnedDocuments = storageStatsCount - numOrphanDocs;
+ const expectedNumOrphanedDocs = numOrphanDocs;
+
+ assert.eq(outputOwnedSizeBytes, expectedOwnedSizeBytes);
+ assert.eq(outputOrphanedSizeBytes, expectedOrphanedSizeBytes);
+ assert.eq(outputNumOwnedDocuments, expectedNumOwnedDocuments);
+ assert.eq(outputNumOrphanedDocs, expectedNumOrphanedDocs);
+ }
+ }
+ }
+}
+
+// Configure initial sharding cluster
+const st = new ShardingTest({shards: 2});
+const mongos = st.s;
+
+const ns1 = "test.foo";
+const ns2 = "bar.baz";
+
+const adminDb = mongos.getDB("admin");
+const testDb = mongos.getDB("test");
+const barDb = mongos.getDB("bar");
+const fooColl = testDb.getCollection("foo");
+const bazColl = barDb.getCollection("baz");
+
+st.adminCommand({enablesharding: testDb.getName(), primaryShard: st.shard1.shardName});
+st.adminCommand({shardcollection: ns1, key: {skey: 1}});
+st.adminCommand({enablesharding: barDb.getName(), primaryShard: st.shard1.shardName});
+st.adminCommand({shardcollection: ns2, key: {skey: 1}});
+
+// Insert data to validate the aggregation stage
+for (let i = 0; i < 6; i++) {
+ assert.commandWorked(fooColl.insert({skey: i}));
+ assert.commandWorked(bazColl.insert({skey: (i + 5)}));
+}
+
+// Test before chunk migration
+testShardedDataAggregationStage();
+
+st.adminCommand({split: ns1, middle: {skey: 2}});
+st.adminCommand({moveChunk: ns1, find: {skey: 2}, to: st.shard0.name, _waitForDelete: true});
+st.adminCommand({split: ns2, middle: {skey: 7}});
+st.adminCommand({moveChunk: ns2, find: {skey: 7}, to: st.shard0.name, _waitForDelete: true});
+
+// Test after chunk migration
+testShardedDataAggregationStage();
+
+// Test invalid queries/values.
+assert.commandFailedWithCode(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: 3}], cursor: {}}),
+ 6789100);
+
+const response = assert.commandFailedWithCode(
+ testDb.runCommand({aggregate: "foo", pipeline: [{$shardedDataDistribution: {}}], cursor: {}}),
+ 6789102);
+assert.neq(-1, response.errmsg.indexOf("$shardedDataDistribution"), response.errmsg);
+assert.neq(-1, response.errmsg.indexOf("admin database"), response.errmsg);
+
+// Test $shardedDataDistribution followed by a $match stage on the 'ns'.
+assert.eq(1, adminDb.aggregate([{$shardedDataDistribution: {}}, {$match: {ns: ns1}}]).itcount());
+assert.eq(2,
+ adminDb.aggregate([{$shardedDataDistribution: {}}, {$match: {ns: {$in: [ns1, ns2]}}}])
+ .itcount());
+assert.eq(0,
+ adminDb.aggregate([{$shardedDataDistribution: {}}, {$match: {ns: 'test.IDoNotExist'}}])
+ .itcount());
+
+// Test $shardedDataDistribution followed by a $match stage on the 'ns' and something else.
+assert.eq(
+ 1,
+ adminDb.aggregate([{$shardedDataDistribution: {}}, {$match: {ns: ns1, shards: {$size: 2}}}])
+ .itcount());
+assert.eq(
+ 0,
+ adminDb.aggregate([{$shardedDataDistribution: {}}, {$match: {ns: ns1, shards: {$size: 50}}}])
+ .itcount());
+
+// Test $shardedDataDistribution followed by a $match stage on the 'ns' and other match stages.
+assert.eq(
+ 1,
+ adminDb
+ .aggregate(
+ [{$shardedDataDistribution: {}}, {$match: {ns: ns1}}, {$match: {shards: {$size: 2}}}])
+ .itcount());
+assert.eq(
+ 0,
+ adminDb
+ .aggregate(
+ [{$shardedDataDistribution: {}}, {$match: {ns: ns1}}, {$match: {shards: {$size: 50}}}])
+ .itcount());
+assert.eq(1,
+ adminDb
+ .aggregate([
+ {$shardedDataDistribution: {}},
+ {$match: {ns: /^test/}},
+ {$match: {shards: {$size: 2}}},
+ {$match: {ns: /foo$/}},
+ ])
+ .itcount());
+
+// Test $shardedDataDistribution followed by a $match stage unrelated to 'ns'.
+assert.eq(
+ 0,
+ adminDb.aggregate([{$shardedDataDistribution: {}}, {$match: {shards: {$size: 50}}}]).itcount());
+
+assert.neq(
+ 0,
+ adminDb.aggregate([{$shardedDataDistribution: {}}, {$match: {shards: {$size: 2}}}]).itcount());
+
+st.stop();
+
+// Test that verifies the behavior in unsharded deployments
+const rsTest = new ReplSetTest({name: 'replicaSetTest', nodes: 2});
+rsTest.startSet();
+rsTest.initiate();
+
+const primary = rsTest.getPrimary();
+const admin = primary.getDB('admin');
+
+const response2 = assert.commandFailedWithCode(
+ admin.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: {}}], cursor: {}}),
+ 6789101);
+assert.neq(
+ -1, response2.errmsg.indexOf("The $shardedDataDistribution stage can only be run on mongoS"));
+
+rsTest.stopSet();
+})();
diff --git a/jstests/sharding/sharded_data_distribution_auth.js b/jstests/sharding/sharded_data_distribution_auth.js
new file mode 100644
index 00000000000..7e63cf19533
--- /dev/null
+++ b/jstests/sharding/sharded_data_distribution_auth.js
@@ -0,0 +1,81 @@
+/*
+ * Test to validate the privileges of using $shardedDataDistribution stage.
+ *
+ * @tags: [
+ * requires_fcv_60,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+if (!TestData.auth) {
+ jsTestLog("Skipping testing authorization since auth is not enabled");
+ return;
+}
+
+// Test privileges
+function testPrivileges() {
+ // Create new role with the exact privileges to execute $shardedDataDistribution
+ assert.commandWorked(adminDb.runCommand({
+ createRole: "role_ok_priv",
+ roles: [],
+ privileges: [{resource: {cluster: true}, actions: ["shardedDataDistribution"]}]
+ }));
+
+ // Creates users with privileges and no privileges
+ assert.commandWorked(adminDb.runCommand({createUser: "user_no_priv", pwd: "pwd", roles: []}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv1", pwd: "pwd", roles: [{role: "role_ok_priv", db: 'admin'}]}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv2", pwd: "pwd", roles: [{role: "clusterMonitor", db: 'admin'}]}));
+
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv1", "pwd"));
+ assert.commandWorked(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: {}}], cursor: {}}));
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv2", "pwd"));
+ assert.commandWorked(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: {}}], cursor: {}}));
+ assert(adminDb.logout());
+
+ // User has no privileges to execute the stage
+ assert(adminDb.auth("user_no_priv", "pwd"));
+ assert.commandFailedWithCode(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: {}}], cursor: {}}),
+ ErrorCodes.Unauthorized,
+ "user should no longer have privileges to execute $shardedDataDistribution stage.");
+ assert(adminDb.logout());
+}
+
+// Configure initial sharding cluster
+const st = new ShardingTest({shards: 1});
+const mongos = st.s;
+
+const ns1 = "test.foo";
+const adminDb = mongos.getDB("admin");
+const testDb = mongos.getDB("test");
+
+// Create a super user with __system role.
+assert.commandWorked(adminDb.runCommand({createUser: "super", pwd: "super", roles: ["__system"]}));
+assert(adminDb.logout());
+assert(adminDb.auth("super", "super"));
+
+st.adminCommand({shardcollection: ns1, key: {skey: 1}});
+
+// Insert data to validate the aggregation stage
+for (let i = 0; i < 6; i++) {
+ assert.commandWorked(testDb.getCollection("foo").insert({skey: i}));
+}
+
+testPrivileges();
+
+st.stop();
+})();
diff --git a/jstests/sharding/sharding_balance1.js b/jstests/sharding/sharding_balance1.js
deleted file mode 100644
index d61d677f1ae..00000000000
--- a/jstests/sharding/sharding_balance1.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * @tags: [
- * requires_fcv_51,
- * ]
- */
-
-(function() {
-'use strict';
-
-load("jstests/sharding/libs/find_chunks_util.js");
-
-var st = new ShardingTest({shards: 2, mongos: 1, other: {chunkSize: 1, enableBalancer: true}});
-
-const dbName = 'ShardingBalanceTest';
-const collName = 'foo';
-const coll = st.getDB(dbName).getCollection(collName);
-const minChunkNum = 20;
-
-assert.commandWorked(st.s.adminCommand({enablesharding: dbName}));
-st.ensurePrimaryShard(dbName, st.shard1.shardName);
-
-const bigStringSize = 10000;
-const bigString = "X=".repeat(bigStringSize / 2);
-
-jsTest.log("Inserting documents that will account for at least 20MB");
-var insertedChars = 0;
-var num = 0;
-var bulk = coll.initializeUnorderedBulkOp();
-while (insertedChars < (minChunkNum * 1024 * 1024)) {
- bulk.insert({_id: num++, s: bigString});
- insertedChars += bigStringSize;
-}
-assert.commandWorked(bulk.execute());
-
-assert.commandWorked(st.s.adminCommand({shardcollection: coll.getFullName(), key: {_id: 1}}));
-jsTest.log("Checking initial chunk distribution: " + st.chunkCounts(collName, dbName));
-assert.lte(minChunkNum,
- findChunksUtil.countChunksForNs(st.config, coll.getFullName()),
- "Number of initial chunks is less then expected");
-assert.lte(minChunkNum,
- st.chunkDiff(collName, dbName),
- "The initial chunks difference between the shards is less then expected");
-
-jsTest.log("Await for the balancer to reduce the chunk imbalance between the shards");
-// Make sure there's enough time here, since balancing can sleep for 15s or so between balances.
-st.awaitBalance(collName, dbName, 1000 * 60 * 5 /* 5 min */);
-
-st.stop();
-})();
diff --git a/jstests/sharding/sharding_balance2.js b/jstests/sharding/sharding_balance2.js
index 4aa528ff6f7..bfb57e1dec6 100644
--- a/jstests/sharding/sharding_balance2.js
+++ b/jstests/sharding/sharding_balance2.js
@@ -5,6 +5,7 @@
'use strict';
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js");
var MaxSizeMB = 1;
@@ -17,6 +18,14 @@ var s = new ShardingTest({
{setParameter: {internalQueryMaxBlockingSortMemoryUsageBytes: 32 * 1024 * 1024}}
}
});
+
+// TODO SERVER-66754 review tests disabled because expecting initial chunks split
+if (FeatureFlagUtil.isEnabled(s.configRS.getPrimary().getDB('admin'), 'NoMoreAutoSplitter')) {
+ jsTestLog("Skipping as featureFlagNoMoreAutoSplitter is enabled");
+ s.stop();
+ return;
+}
+
var db = s.getDB("test");
var names = s.getConnNames();
diff --git a/jstests/sharding/sharding_balance3.js b/jstests/sharding/sharding_balance3.js
index c40699d0f82..b13751bd537 100644
--- a/jstests/sharding/sharding_balance3.js
+++ b/jstests/sharding/sharding_balance3.js
@@ -3,6 +3,7 @@
(function() {
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js");
var s = new ShardingTest({
name: "slow_sharding_balance3",
@@ -16,6 +17,13 @@ var s = new ShardingTest({
}
});
+// TODO SERVER-66754 review tests disabled because expecting initial chunks split
+if (FeatureFlagUtil.isEnabled(s.configRS.getPrimary().getDB('admin'), 'NoMoreAutoSplitter')) {
+ jsTestLog("Skipping as featureFlagNoMoreAutoSplitter is enabled");
+ s.stop();
+ return;
+}
+
s.adminCommand({enablesharding: "test"});
s.ensurePrimaryShard('test', s.shard1.shardName);
diff --git a/jstests/sharding/sharding_balance4.js b/jstests/sharding/sharding_balance4.js
deleted file mode 100644
index de54e9460cd..00000000000
--- a/jstests/sharding/sharding_balance4.js
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * Check that doing updates done during a migrate all go to the right place
- *
- * This test is labeled resource intensive because its total io_write is 36MB compared to a median
- * of 5MB across all sharding tests in wiredTiger.
- * @tags: [resource_intensive]
- */
-(function() {
-load('jstests/sharding/autosplit_include.js');
-load("jstests/sharding/libs/find_chunks_util.js");
-
-var s = new ShardingTest({shards: 2, mongos: 1, other: {chunkSize: 1, enableAutoSplit: true}});
-
-// Double the balancer interval to produce fewer migrations per unit time so that the test does not
-// run out of stale shard version retries.
-s.forEachConfigServer((conn) => {
- conn.adminCommand({
- configureFailPoint: 'overrideBalanceRoundInterval',
- mode: 'alwaysOn',
- data: {intervalMs: 2000}
- });
-});
-
-assert.commandWorked(s.s0.adminCommand({enablesharding: "test"}));
-s.ensurePrimaryShard('test', s.shard1.shardName);
-assert.commandWorked(s.s0.adminCommand({shardcollection: "test.foo", key: {_id: 1}}));
-
-s.config.settings.find().forEach(printjson);
-
-db = s.getDB("test");
-
-bigString = "";
-while (bigString.length < 10000)
- bigString += "asdasdasdasdadasdasdasdasdasdasdasdasda";
-
-N = 3000;
-
-num = 0;
-
-var counts = {};
-
-//
-// TODO: Rewrite to make much clearer.
-//
-// The core behavior of this test is to add a bunch of documents to a sharded collection, then
-// incrementally update each document and make sure the counts in the document match our update
-// counts while balancing occurs (doUpdate()). Every once in a while we also check (check())
-// our counts via a query.
-//
-// If during a chunk migration an update is missed, we trigger an assertion and fail.
-//
-
-function doUpdate(bulk, includeString, optionalId) {
- var up = {$inc: {x: 1}};
- if (includeString) {
- up["$set"] = {s: bigString};
- }
- var myid = optionalId == undefined ? Random.randInt(N) : optionalId;
- bulk.find({_id: myid}).upsert().update(up);
-
- counts[myid] = (counts[myid] ? counts[myid] : 0) + 1;
- return myid;
-}
-
-Random.setRandomSeed();
-
-// Initially update all documents from 1 to N, otherwise later checks can fail because no
-// document previously existed
-for (i = 0; i < N; i++) {
- let bulk = db.foo.initializeUnorderedBulkOp();
- doUpdate(bulk, true, i);
- assert.commandWorked(bulk.execute());
- waitForOngoingChunkSplits(s);
-}
-
-for (i = 0; i < N * 9; i++) {
- let bulk = db.foo.initializeUnorderedBulkOp();
- doUpdate(bulk, false);
- assert.commandWorked(bulk.execute());
- waitForOngoingChunkSplits(s);
-}
-
-for (var i = 0; i < 50; i++) {
- s.printChunks("test.foo");
- if (check("initial:" + i, true))
- break;
- sleep(5000);
-}
-check("initial at end");
-
-assert.lt(20, findChunksUtil.countChunksForNs(s.config, "test.foo"), "setup2");
-
-function check(msg, dontAssert) {
- for (var x in counts) {
- var e = counts[x];
- var z = db.foo.findOne({_id: parseInt(x)});
-
- if (z && z.x == e)
- continue;
-
- if (dontAssert) {
- if (z)
- delete z.s;
- print("not asserting for key failure: " + x + " want: " + e + " got: " + tojson(z));
- return false;
- }
-
- s.s.getDB("admin").runCommand({setParameter: 1, logLevel: 2});
-
- printjson(db.foo.findOne({_id: parseInt(x)}));
-
- var y = db.foo.findOne({_id: parseInt(x)});
-
- if (y) {
- delete y.s;
- }
-
- s.printChunks("test.foo");
-
- assert(z, "couldn't find : " + x + " y:" + tojson(y) + " e: " + e + " " + msg);
- assert.eq(e, z.x, "count for : " + x + " y:" + tojson(y) + " " + msg);
- }
-
- return true;
-}
-
-var consecutiveNoProgressMadeErrors = 0;
-
-function diff1() {
- jsTest.log("Running diff1...");
-
- var bulk = db.foo.initializeUnorderedBulkOp();
- var myid = doUpdate(bulk, false);
- var res = bulk.execute();
-
- assert(res instanceof BulkWriteResult,
- 'Result from bulk.execute should be of type BulkWriteResult');
- if (res.hasWriteErrors()) {
- res.writeErrors.forEach(function(err) {
- // Ignore up to 3 consecutive NoProgressMade errors for the cases where migration
- // might be going faster than the writes are executing
- if (err.code == ErrorCodes.NoProgressMade) {
- consecutiveNoProgressMadeErrors++;
- if (consecutiveNoProgressMadeErrors < 3) {
- return;
- }
- }
-
- assert.commandWorked(res);
- });
- } else {
- consecutiveNoProgressMadeErrors = 0;
-
- assert.eq(1,
- res.nModified,
- "diff myid: " + myid + " 2: " + res.toString() + "\n" +
- " correct count is: " + counts[myid] +
- " db says count is: " + tojson(db.foo.findOne({_id: myid})));
- }
-
- var x = s.chunkCounts("foo");
- if (Math.random() > .999)
- printjson(x);
-
- return Math.max(x[s.shard0.shardName], x[s.shard1.shardName]) -
- Math.min(x[s.shard0.shardName], x[s.shard1.shardName]);
-}
-
-assert.lt(20, diff1(), "initial load");
-print(diff1());
-
-s.startBalancer();
-
-assert.soon(function() {
- var d = diff1();
- return d < 5;
-}, "balance didn't happen", 1000 * 60 * 20, 1);
-
-s.stop();
-})();
diff --git a/jstests/sharding/sharding_migrate_cursor1.js b/jstests/sharding/sharding_migrate_cursor1.js
index 3bac25b5e34..c0ef8d391fd 100644
--- a/jstests/sharding/sharding_migrate_cursor1.js
+++ b/jstests/sharding/sharding_migrate_cursor1.js
@@ -7,10 +7,18 @@
*/
(function() {
+load("jstests/libs/feature_flag_util.js");
+
var chunkSize = 25;
var s = new ShardingTest(
{name: "migrate_cursor1", shards: 2, mongos: 1, other: {chunkSize: chunkSize}});
+// TODO SERVER-66754 review tests disabled because expecting initial chunks split
+if (FeatureFlagUtil.isEnabled(s.configRS.getPrimary().getDB('admin'), 'NoMoreAutoSplitter')) {
+ jsTestLog("Skipping as featureFlagNoMoreAutoSplitter is enabled");
+ s.stop();
+ return;
+}
s.adminCommand({enablesharding: "test"});
db = s.getDB("test");
diff --git a/jstests/sharding/sharding_statistics_server_status.js b/jstests/sharding/sharding_statistics_server_status.js
index 67e22039403..26bde84be4b 100644
--- a/jstests/sharding/sharding_statistics_server_status.js
+++ b/jstests/sharding/sharding_statistics_server_status.js
@@ -18,7 +18,7 @@ function ShardStat() {
this.countRecipientMoveChunkStarted = 0;
this.countDocsClonedOnRecipient = 0;
this.countDocsClonedOnDonor = 0;
- this.countDocsDeletedOnDonor = 0;
+ this.countDocsDeletedByRangeDeleter = 0;
}
function incrementStatsAndCheckServerShardStats(donor, recipient, numDocs) {
@@ -26,11 +26,14 @@ function incrementStatsAndCheckServerShardStats(donor, recipient, numDocs) {
donor.countDocsClonedOnDonor += numDocs;
++recipient.countRecipientMoveChunkStarted;
recipient.countDocsClonedOnRecipient += numDocs;
- donor.countDocsDeletedOnDonor += numDocs;
+ donor.countDocsDeletedByRangeDeleter += numDocs;
const statsFromServerStatus = shardArr.map(function(shardVal) {
return shardVal.getDB('admin').runCommand({serverStatus: 1}).shardingStatistics;
});
for (let i = 0; i < shardArr.length; ++i) {
+ let countDocsDeleted = statsFromServerStatus[i].hasOwnProperty('countDocsDeletedOnDonor')
+ ? statsFromServerStatus[i].countDocsDeletedOnDonor
+ : statsFromServerStatus[i].countDocsDeletedByRangeDeleter;
assert(statsFromServerStatus[i]);
assert(statsFromServerStatus[i].countStaleConfigErrors);
assert(statsFromServerStatus[i].totalCriticalSectionCommitTimeMillis);
@@ -43,8 +46,7 @@ function incrementStatsAndCheckServerShardStats(donor, recipient, numDocs) {
assert.eq(stats[i].countDocsClonedOnRecipient,
statsFromServerStatus[i].countDocsClonedOnRecipient);
assert.eq(stats[i].countDocsClonedOnDonor, statsFromServerStatus[i].countDocsClonedOnDonor);
- assert.eq(stats[i].countDocsDeletedOnDonor,
- statsFromServerStatus[i].countDocsDeletedOnDonor);
+ assert.eq(stats[i].countDocsDeletedByRangeDeleter, countDocsDeleted);
assert.eq(stats[i].countRecipientMoveChunkStarted,
statsFromServerStatus[i].countRecipientMoveChunkStarted);
}
@@ -64,6 +66,13 @@ function checkServerStatusAbortedMigrationCount(shardConn, count) {
assert.eq(count, shardStats.countDonorMoveChunkAbortConflictingIndexOperation);
}
+function checkServerStatusNumShardedCollections(conn, count) {
+ const shardStats =
+ assert.commandWorked(conn.adminCommand({serverStatus: 1})).shardingStatistics;
+ assert(shardStats.hasOwnProperty("numShardedCollections"));
+ assert.eq(count, shardStats.numShardedCollections);
+}
+
function runConcurrentMoveChunk(host, ns, toShard) {
const mongos = new Mongo(host);
// Helper function to run moveChunk, retrying on ConflictingOperationInProgress. We need to
@@ -132,6 +141,17 @@ st.ensurePrimaryShard(coll.getDB() + "", st.shard0.shardName);
assert.commandWorked(admin.runCommand({shardCollection: coll + "", key: {_id: 1}}));
assert.commandWorked(admin.runCommand({split: coll + "", middle: {_id: 0}}));
+// Check the number of sharded collections.
+const testDB = st.rs0.getPrimary().getDB(dbName);
+const fcvDoc = testDB.adminCommand({getParameter: 1, featureCompatibilityVersion: 1});
+if (MongoRunner.compareBinVersions(fcvDoc.featureCompatibilityVersion.version, '6.0') >= 0) {
+ st.shardColl(dbName + ".coll2", {_id: 1}, false);
+ st.shardColl(dbName + ".coll3", {_id: 1}, false);
+ const configCollections = mongos.getCollection("config.collections");
+ checkServerStatusNumShardedCollections(st.configRS.getPrimary(),
+ configCollections.countDocuments({}));
+}
+
// Move chunk from shard0 to shard1 without docs.
assert.commandWorked(
mongos.adminCommand({moveChunk: coll + '', find: {_id: 1}, to: st.shard1.shardName}));
diff --git a/jstests/sharding/stale_mongos_updates_and_removes.js b/jstests/sharding/stale_mongos_updates_and_removes.js
index 06878fe9177..ca3617cd24a 100644
--- a/jstests/sharding/stale_mongos_updates_and_removes.js
+++ b/jstests/sharding/stale_mongos_updates_and_removes.js
@@ -132,20 +132,16 @@ function checkAllRemoveQueries(makeMongosStaleFunc) {
assert.writeError(res);
}
- // Not possible because single remove requires equality match on shard key.
- checkRemoveIsInvalid(emptyQuery, single, makeMongosStaleFunc);
+ doRemove(emptyQuery, single, makeMongosStaleFunc);
doRemove(emptyQuery, multi, makeMongosStaleFunc);
doRemove(pointQuery, single, makeMongosStaleFunc);
doRemove(pointQuery, multi, makeMongosStaleFunc);
- // Not possible because can't do range query on a single remove.
- checkRemoveIsInvalid(rangeQuery, single, makeMongosStaleFunc);
+ doRemove(rangeQuery, single, makeMongosStaleFunc);
doRemove(rangeQuery, multi, makeMongosStaleFunc);
- // Not possible because single remove must contain _id or shard key at top level
- // (not within $or).
- checkRemoveIsInvalid(multiPointQuery, single, makeMongosStaleFunc);
+ doRemove(multiPointQuery, single, makeMongosStaleFunc);
doRemove(multiPointQuery, multi, makeMongosStaleFunc);
}
diff --git a/jstests/sharding/standalone_in_queryable_backup_mode.js b/jstests/sharding/standalone_in_queryable_backup_mode.js
new file mode 100644
index 00000000000..e68bd41dbb0
--- /dev/null
+++ b/jstests/sharding/standalone_in_queryable_backup_mode.js
@@ -0,0 +1,99 @@
+/**
+ * The goal of this test is to apply some oplog entries during the startup of a mongod process
+ * configured with --shardsvr and --queryableBackupMode.
+ *
+ * @tags: [
+ * # In-memory storage engine does not support queryable backups.
+ * requires_persistence,
+ * # Config shards do not support queryable backups.
+ * config_shard_incompatible
+ * ]
+ */
+
+// This test shuts down a shard.
+TestData.skipCheckingUUIDsConsistentAcrossCluster = true;
+TestData.skipCheckingIndexesConsistentAcrossCluster = true;
+TestData.skipCheckDBHashes = true;
+TestData.skipCheckOrphans = true;
+TestData.skipCheckShardFilteringMetadata = true;
+TestData.skipCheckMetadataConsistency = true;
+
+(function() {
+'use strict';
+
+const st = new ShardingTest({
+ mongos: 1,
+ shards: 1,
+ rs: {nodes: 2},
+});
+
+jsTest.log("Going to set up the environment");
+var kDbName = 'testDb';
+var kShardedCollName = 'testShardedColl';
+var kUnshardedCollName = 'testUnshardedColl';
+
+assert.commandWorked(st.s.adminCommand({enableSharding: kDbName}));
+assert.commandWorked(
+ st.s.adminCommand({shardCollection: kDbName + '.' + kShardedCollName, key: {_id: 1}}));
+
+const recoveryTimestamp =
+ assert.commandWorked(st.rs0.getPrimary().getDB(kDbName).runCommand({ping: 1})).operationTime;
+
+jsTest.log("Going to hold the stable timestamp of the secondary node at " +
+ tojson(recoveryTimestamp));
+// Hold back the recovery timestamp before doing another write so we have some oplog entries to
+// apply when restart in queryableBackupMode with recoverToOplogTimestamp.
+assert.commandWorked(st.rs0.getSecondary().getDB('admin').adminCommand({
+ "configureFailPoint": 'holdStableTimestampAtSpecificTimestamp',
+ "mode": 'alwaysOn',
+ "data": {"timestamp": recoveryTimestamp}
+}));
+
+jsTest.log("Going to apply some CRUD operations over sharded and unsharded collections");
+function applyCRUDOnColl(coll) {
+ coll.insert({age: 42});
+ coll.update({age: 42}, {$set: {name: "john"}});
+ coll.deleteMany({});
+}
+applyCRUDOnColl(st.s.getDB(kDbName)[kShardedCollName]);
+applyCRUDOnColl(st.s.getDB(kDbName)[kUnshardedCollName]);
+st.rs0.awaitReplication();
+
+jsTest.log("Going to stop the secondary node of the shard");
+const operationTime =
+ assert.commandWorked(st.rs0.getPrimary().getDB(kDbName).runCommand({ping: 1})).operationTime;
+const secondaryPort = st.rs0.getSecondary().port;
+const secondaryDbPath = st.rs0.getSecondary().dbpath;
+MongoRunner.stopMongod(st.rs0.getSecondary());
+
+jsTest.log(
+ "Going to start a mongod process with --shardsvr, --queryableBackupMode and recoverToOplogTimestamp");
+const shardIdentity = st.rs0.getPrimary().getDB("admin").getCollection("system.version").findOne({
+ _id: "shardIdentity"
+});
+let configFileStr =
+ "sharding:\n _overrideShardIdentity: '" + tojson(shardIdentity).replace(/\s+/g, ' ') + "'";
+let delim = _isWindows() ? '\\' : '/';
+let configFilePath = secondaryDbPath + delim + "config-for-read-only-mongod.yml";
+writeFile(configFilePath, configFileStr);
+
+const newMongoD = MongoRunner.runMongod({
+ config: configFilePath,
+ dbpath: secondaryDbPath,
+ port: secondaryPort,
+ noCleanData: true,
+ setParameter: {recoverToOplogTimestamp: tojson({timestamp: operationTime})},
+ queryableBackupMode: "",
+ shardsvr: "",
+});
+
+jsTest.log("Going to verify the number of documents of both collections");
+assert.eq(newMongoD.getDB(kDbName)[kShardedCollName].find({}).itcount(), 0);
+assert.eq(newMongoD.getDB(kDbName)[kUnshardedCollName].find({}).itcount(), 0);
+
+jsTest.log("Going to stop the shardsvr queryable backup mode mongod process");
+MongoRunner.stopMongod(newMongoD);
+
+jsTest.log("Going to stop the sharding test");
+st.stop();
+})();
diff --git a/jstests/sharding/timeseries_cluster_collstats.js b/jstests/sharding/timeseries_cluster_collstats.js
index b672c67a3fe..8d5b196d8e7 100644
--- a/jstests/sharding/timeseries_cluster_collstats.js
+++ b/jstests/sharding/timeseries_cluster_collstats.js
@@ -1,6 +1,7 @@
/**
* Tests that the cluster collStats command returns timeseries statistics in the expected format.
*
+ * For legacy collStats command:
* {
* ....,
* "ns" : ...,
@@ -20,15 +21,43 @@
* ....
* }
*
+ * For aggregate $collStats stage:
+ * [
+ * {
+ * ....,
+ * "ns" : ...,
+ * "shard" : ...,
+ * "latencyStats" : {
+ * ....
+ * },
+ * "storageStats" : {
+ * ...,
+ * "timeseries" : {
+ * ...,
+ * },
+ * },
+ * "count" : {
+ * ....
+ * },
+ * "queryExecStats" : {
+ * ....
+ * },
+ * },
+ * {
+ * .... (Other shard's result)
+ * },
+ * ...
+ * ]
+ *
* @tags: [
- * requires_fcv_51
+ * requires_fcv_60,
* ]
*/
(function() {
load("jstests/core/timeseries/libs/timeseries.js");
-
-const st = new ShardingTest({shards: 2});
+const numShards = 2;
+const st = new ShardingTest({shards: numShards});
if (!TimeseriesTest.shardedtimeseriesCollectionsEnabled(st.shard0)) {
jsTestLog("Skipping test because the sharded time-series collection feature flag is disabled");
@@ -83,14 +112,14 @@ assert.commandWorked(st.s.adminCommand({
key: {[metaField]: 1},
}));
-// Force splitting two chunks.
+// Force splitting numShards chunks.
const splitPoint = {
- meta: numberDoc / 2
+ meta: numberDoc / numShards
};
assert.commandWorked(st.s.adminCommand({split: bucketNs, middle: splitPoint}));
// Ensure that currently both chunks reside on the primary shard.
let counts = st.chunkCounts(`system.buckets.${collName}`, dbName);
-assert.eq(2, counts[primaryShard.shardName]);
+assert.eq(numShards, counts[primaryShard.shardName]);
// Move one of the chunks into the second shard.
assert.commandWorked(st.s.adminCommand(
{movechunk: bucketNs, find: splitPoint, to: otherShard.name, _waitForDelete: true}));
@@ -105,20 +134,13 @@ for (let i = 0; i < numberDoc; i++) {
}
assert.eq(mongosColl.find().itcount(), numberDoc * 2);
-clusterCollStatsResult = assert.commandWorked(mongosDB.runCommand({collStats: collName}));
-jsTestLog("Sharded cluster collStats command result: " + tojson(clusterCollStatsResult));
+function checkAllFieldsAreInResult(result) {
+ assert(result.hasOwnProperty("latencyStats"), result);
+ assert(result.hasOwnProperty("storageStats"), result);
+ assert(result.hasOwnProperty("count"), result);
+ assert(result.hasOwnProperty("queryExecStats"), result);
+}
-// Check that the top-level 'timeseries' fields match the sum of two shard's, that the stats were
-// correctly aggregated.
-assert(clusterCollStatsResult.shards[primaryShard.shardName].timeseries,
- "Expected a shard 'timeseries' field on shard " + primaryShard.shardName +
- " but didn't find one: " + tojson(clusterCollStatsResult));
-assert(clusterCollStatsResult.shards[otherShard.shardName].timeseries,
- "Expected a shard 'timeseries' field on shard " + otherShard.shardName +
- " but didn't find one: " + tojson(clusterCollStatsResult));
-assert(clusterCollStatsResult.timeseries,
- "Expected an aggregated 'timeseries' field but didn't find one: " +
- tojson(clusterCollStatsResult));
function assertTimeseriesAggregationCorrectness(total, shards) {
assert(shards.every(x => x.bucketNs === total.bucketNs));
assert.eq(total.bucketCount,
@@ -166,10 +188,69 @@ function assertTimeseriesAggregationCorrectness(total, shards) {
assert(total.numCommits > 0);
assert(total.numMeasurementsCommitted > 0);
}
-assertTimeseriesAggregationCorrectness(clusterCollStatsResult.timeseries, [
- clusterCollStatsResult.shards[primaryShard.shardName].timeseries,
- clusterCollStatsResult.shards[otherShard.shardName].timeseries
-]);
+
+function verifyClusterCollStatsResult(
+ clusterCollStatsResult, sumTimeseriesStatsAcrossShards, isAggregation) {
+ if (isAggregation) {
+ // $collStats should output one document per shard.
+ assert.eq(clusterCollStatsResult.length,
+ numShards,
+ "Expected " + numShards +
+ "documents to be returned: " + tojson(clusterCollStatsResult));
+
+ checkAllFieldsAreInResult(clusterCollStatsResult[0]);
+ checkAllFieldsAreInResult(clusterCollStatsResult[1]);
+ }
+
+ assert(sumTimeseriesStatsAcrossShards,
+ "Expected an aggregated 'timeseries' field but didn't find one: " +
+ tojson(clusterCollStatsResult));
+
+ const primaryShardStats = isAggregation
+ ? clusterCollStatsResult[0].storageStats.timeseries
+ : clusterCollStatsResult.shards[primaryShard.shardName].timeseries;
+
+ const otherShardStats = isAggregation
+ ? clusterCollStatsResult[1].storageStats.timeseries
+ : clusterCollStatsResult.shards[otherShard.shardName].timeseries;
+
+ // Check that the top-level 'timeseries' fields match the sum of two shard's, that the stats
+ // were correctly aggregated.
+ assert(primaryShardStats,
+ "Expected a shard 'timeseries' field on shard " + primaryShard.shardName +
+ " but didn't find one: " + tojson(clusterCollStatsResult));
+ assert(otherShardStats,
+ "Expected a shard 'timeseries' field on shard " + otherShard.shardName +
+ " but didn't find one: " + tojson(clusterCollStatsResult));
+
+ assertTimeseriesAggregationCorrectness(sumTimeseriesStatsAcrossShards,
+ [primaryShardStats, otherShardStats]);
+}
+
+// Tests that the output of the collStats command returns results from both the shards and
+// includes all the expected fields.
+clusterCollStatsResult = assert.commandWorked(mongosDB.runCommand({collStats: collName}));
+jsTestLog("Sharded cluster collStats command result: " + tojson(clusterCollStatsResult));
+const sumTimeseriesStatsAcrossShards = clusterCollStatsResult.timeseries;
+verifyClusterCollStatsResult(
+ clusterCollStatsResult, sumTimeseriesStatsAcrossShards, false // isAggregation
+);
+
+// Tests that the output of the $collStats stage returns results from both the shards and includes
+// all the expected fields.
+clusterCollStatsResult =
+ mongosColl
+ .aggregate(
+ [{$collStats: {latencyStats: {}, storageStats: {}, count: {}, queryExecStats: {}}}])
+ .toArray();
+jsTestLog("Sharded cluster collStats aggregation result: " + tojson(clusterCollStatsResult));
+
+// Use the same sumTimeseriesStatsAcrossShards value as the collStats command since
+// aggregation does not sum up timeseries stats results. This will also verify that the results
+// output by collStats in find and aggregation are the same.
+verifyClusterCollStatsResult(
+ clusterCollStatsResult, sumTimeseriesStatsAcrossShards, true // isAggregation
+);
st.stop();
})();
diff --git a/jstests/sharding/timeseries_cluster_indexstats.js b/jstests/sharding/timeseries_cluster_indexstats.js
index f734d8b03b4..5c480cdf65f 100644
--- a/jstests/sharding/timeseries_cluster_indexstats.js
+++ b/jstests/sharding/timeseries_cluster_indexstats.js
@@ -59,13 +59,7 @@ function checkIndexStats(coll, keys, sharded) {
keys.length,
`There should be ${keys.length} indices on the collection.\n${tojson(indices)}`);
indices.forEach((index, i) => {
- assert.eq(index.hasOwnProperty('shard'),
- sharded,
- sharded
- ? `Index stats 'shard' field should exist on a sharded collection.\n${
- tojson(index)}`
- : `Index stats 'shard' field should not exist on a non-sharded collection.\n${
- tojson(index)}`);
+ assert(index.hasOwnProperty('shard'), tojson(index));
assert.docEq(
index.key, keys[i], `Index should have key spec ${tojson(keys[i])}.\n${tojson(index)}`);
});
diff --git a/jstests/sharding/timeseries_coll_mod.js b/jstests/sharding/timeseries_coll_mod.js
index 2627fa21cf4..36b33cb3e04 100644
--- a/jstests/sharding/timeseries_coll_mod.js
+++ b/jstests/sharding/timeseries_coll_mod.js
@@ -68,11 +68,10 @@ function runBasicTest(failPoint) {
key: {[metaField]: 1},
}));
- // Normal collMod commands works for the sharded time-series collection.
- assert.commandWorked(
- db.runCommand({collMod: collName, index: {name: indexName, hidden: true}}));
- assert.commandWorked(
- db.runCommand({collMod: collName, index: {name: indexName, hidden: false}}));
+ // Check that collMod commands works for the sharded time-series collection.
+ assert.commandWorked(db[collName].createIndex({'a': 1}));
+ assert.commandWorked(db.runCommand({collMod: collName, index: {name: 'a_1', hidden: true}}));
+ assert.commandWorked(db.runCommand({collMod: collName, index: {name: 'a_1', hidden: false}}));
if (failPoint) {
// Granularity update disabled for sharded time-series collection, when we're using primary
diff --git a/jstests/sharding/timeseries_query.js b/jstests/sharding/timeseries_query.js
index 4cf3b1c28c5..7a8b835c876 100644
--- a/jstests/sharding/timeseries_query.js
+++ b/jstests/sharding/timeseries_query.js
@@ -122,7 +122,7 @@ function runQuery(
if (expectCollScan) {
assert(isCollscan(sDB, winningPlan));
} else {
- assert(isIxscan(sDB, winningPlan));
+ assert(isIxscan(sDB, winningPlan) || isClusteredIxscan(sDB, winningPlan));
}
});
});
diff --git a/jstests/sharding/timeseries_sharding_admin_commands.js b/jstests/sharding/timeseries_sharding_admin_commands.js
index 696fa86ee69..bfd8d3ef853 100644
--- a/jstests/sharding/timeseries_sharding_admin_commands.js
+++ b/jstests/sharding/timeseries_sharding_admin_commands.js
@@ -274,17 +274,5 @@ function assertRangeMatch(savedRange, paramRange) {
dropTimeSeriesColl();
})();
-// Check renameCollection command cannot modify name through the view namespace.
-(function checkRenameCollectionCommand() {
- createTimeSeriesColl(
- {index: {[metaField]: 1, [timeField]: 1}, shardKey: {[metaField]: 1, [timeField]: 1}});
- const newCollName = `${collName}New`;
- const newViewNss = `${dbName}.${newCollName}`;
- // Rename collection is not supported through view namespace.
- assert.commandFailedWithCode(mongo.s.adminCommand({renameCollection: viewNss, to: newViewNss}),
- ErrorCodes.NamespaceNotFound);
- dropTimeSeriesColl();
-})();
-
mongo.stop();
})();
diff --git a/jstests/sharding/timeseries_update.js b/jstests/sharding/timeseries_update.js
index 96531a09c56..0ec84933dab 100644
--- a/jstests/sharding/timeseries_update.js
+++ b/jstests/sharding/timeseries_update.js
@@ -23,7 +23,8 @@ const dbName = 'testDB';
const collName = 'coll';
const timeField = "time";
const metaField = "tag";
-const dateTime = ISODate("2021-07-12T16:00:00Z");
+const dateTime1 = ISODate("2021-07-12T16:00:00Z");
+const dateTime2 = ISODate("2021-07-13T16:00:00Z");
//
// Checks for feature flags.
@@ -66,29 +67,29 @@ if (!TimeseriesTest.shardedTimeseriesUpdatesAndDeletesEnabled(st.shard0)) {
const doc1 = {
_id: 1,
- [timeField]: dateTime,
+ [timeField]: dateTime1,
[metaField]: {a: "A", b: "B"}
};
const doc2 = {
_id: 2,
- [timeField]: dateTime,
+ [timeField]: dateTime2,
[metaField]: {c: "C", d: 2},
f: [{"k": "K", "v": "V"}]
};
const doc3 = {
_id: 3,
- [timeField]: dateTime,
+ [timeField]: dateTime1,
f: "F"
};
const doc4 = {
_id: 4,
- [timeField]: dateTime,
+ [timeField]: dateTime1,
[metaField]: {a: "A", b: "B"},
f: "F"
};
const doc5 = {
_id: 5,
- [timeField]: dateTime,
+ [timeField]: dateTime1,
[metaField]: {a: "A", b: "B", c: "C"}
};
@@ -407,7 +408,7 @@ function testCaseBatchUpdates({testUpdate}) {
],
resultDocList: [{
_id: 2,
- [timeField]: dateTime,
+ [timeField]: dateTime1,
[metaField]: 3,
f: [{"k": "K", "v": "V"}],
}],
@@ -455,7 +456,7 @@ function testCaseBatchUpdates({testUpdate}) {
],
resultDocList: [{
_id: 2,
- [timeField]: dateTime,
+ [timeField]: dateTime1,
[metaField]: {c: "C", d: 8},
f: [{"k": "K", "v": "V"}],
}],
@@ -506,7 +507,7 @@ function testCaseBatchUpdates({testUpdate}) {
],
resultDocList: [{
_id: 2,
- [timeField]: dateTime,
+ [timeField]: dateTime2,
[metaField]: {c: "C", d: 15},
f: [{"k": "K", "v": "V"}],
}],
@@ -526,7 +527,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$rename: {[metaField + ".a"]: metaField + ".z"}},
multi: true,
}],
- resultDocList: [{_id: 1, [timeField]: dateTime, [metaField]: {z: "A", b: "B"}}, doc2],
+ resultDocList: [{_id: 1, [timeField]: dateTime1, [metaField]: {z: "A", b: "B"}}, doc2],
n: 1,
pathToMetaFieldBeingUpdated: "a",
});
@@ -539,7 +540,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$set: {[metaField]: {c: "C"}}},
multi: true,
}],
- resultDocList: [{_id: 1, [timeField]: dateTime, [metaField]: {c: "C"}}],
+ resultDocList: [{_id: 1, [timeField]: dateTime1, [metaField]: {c: "C"}}],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -554,7 +555,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
}],
resultDocList: [
doc1,
- {_id: 2, [timeField]: dateTime, [metaField]: {c: 1, d: 2}, f: [{"k": "K", "v": "V"}]},
+ {_id: 2, [timeField]: dateTime2, [metaField]: {c: 1, d: 2}, f: [{"k": "K", "v": "V"}]},
doc4,
doc5
],
@@ -572,10 +573,15 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
multi: true,
}],
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: {b: "B"}},
- {_id: 2, [timeField]: dateTime, [metaField]: {c: "C", d: 2}, f: [{"k": "K", "v": "V"}]},
- {_id: 4, [timeField]: dateTime, [metaField]: {b: "B"}, f: "F"},
- {_id: 5, [timeField]: dateTime, [metaField]: {b: "B", c: "C"}}
+ {_id: 1, [timeField]: dateTime1, [metaField]: {b: "B"}},
+ {
+ _id: 2,
+ [timeField]: dateTime1,
+ [metaField]: {c: "C", d: 2},
+ f: [{"k": "K", "v": "V"}]
+ },
+ {_id: 4, [timeField]: dateTime1, [metaField]: {b: "B"}, f: "F"},
+ {_id: 5, [timeField]: dateTime1, [metaField]: {b: "B", c: "C"}}
],
ordered: false,
n: 3,
@@ -590,7 +596,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$set: {[metaField]: {c: "C"}}},
multi: true,
}],
- resultDocList: [{_id: 1, [timeField]: dateTime, [metaField]: {c: "C"}}],
+ resultDocList: [{_id: 1, [timeField]: dateTime1, [metaField]: {c: "C"}}],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -603,7 +609,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$set: {[metaField]: {c: "C"}}},
multi: true,
}],
- resultDocList: [{_id: 1, [timeField]: dateTime, [metaField]: {c: "C"}}],
+ resultDocList: [{_id: 1, [timeField]: dateTime1, [metaField]: {c: "C"}}],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -616,9 +622,12 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$inc: {[metaField + ".d"]: 10}},
multi: true,
}],
- resultDocList: [
- {_id: 2, [timeField]: dateTime, [metaField]: {c: "C", d: 12}, f: [{"k": "K", "v": "V"}]}
- ],
+ resultDocList: [{
+ _id: 2,
+ [timeField]: dateTime2,
+ [metaField]: {c: "C", d: 12},
+ f: [{"k": "K", "v": "V"}]
+ }],
n: 1,
pathToMetaFieldBeingUpdated: "d",
});
@@ -632,8 +641,8 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
multi: true,
}],
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: {z: "Z"}},
- {_id: 2, [timeField]: dateTime, [metaField]: {z: "Z"}, f: [{"k": "K", "v": "V"}]}
+ {_id: 1, [timeField]: dateTime1, [metaField]: {z: "Z"}},
+ {_id: 2, [timeField]: dateTime2, [metaField]: {z: "Z"}, f: [{"k": "K", "v": "V"}]}
],
n: 2,
pathToMetaFieldBeingUpdated: "",
@@ -644,7 +653,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
initialDocList: [doc1],
updates:
[{q: {[metaField]: {a: "A", b: "B"}}, u: {$unset: {[metaField]: ""}}, multi: true}],
- resultDocList: [{_id: 1, [timeField]: dateTime}],
+ resultDocList: [{_id: 1, [timeField]: dateTime1}],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -660,9 +669,9 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
},
],
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: {a: "A", b: "B", c: "C"}},
- {_id: 4, [timeField]: dateTime, [metaField]: {a: "A", b: "B", c: "C"}, f: "F"},
- {_id: 5, [timeField]: dateTime, [metaField]: {a: "A", b: "B", c: "C"}}
+ {_id: 1, [timeField]: dateTime1, [metaField]: {a: "A", b: "B", c: "C"}},
+ {_id: 4, [timeField]: dateTime1, [metaField]: {a: "A", b: "B", c: "C"}, f: "F"},
+ {_id: 5, [timeField]: dateTime1, [metaField]: {a: "A", b: "B", c: "C"}}
],
n: 3,
nModified: 2,
@@ -678,8 +687,8 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
multi: true
}],
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: "a"},
- {_id: 2, [timeField]: dateTime, [metaField]: "a", f: [{"k": "K", "v": "V"}]},
+ {_id: 1, [timeField]: dateTime1, [metaField]: "a"},
+ {_id: 2, [timeField]: dateTime2, [metaField]: "a", f: [{"k": "K", "v": "V"}]},
doc3
],
n: 2,
@@ -694,7 +703,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$set: {[metaField]: "a"}},
multi: true
}],
- resultDocList: [{_id: 1, [timeField]: dateTime, [metaField]: "a"}, doc2, doc3],
+ resultDocList: [{_id: 1, [timeField]: dateTime1, [metaField]: "a"}, doc2, doc3],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -709,7 +718,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
},
expectFailedUpdate([doc1, doc2, doc3]));
- const nestedMetaObj = {_id: 6, [timeField]: dateTime, [metaField]: {[metaField]: "A", a: 1}};
+ const nestedMetaObj = {_id: 6, [timeField]: dateTime1, [metaField]: {[metaField]: "A", a: 1}};
// Query for documents using $jsonSchema with the metaField required and a required subfield of
// the metaField with the same name as the metaField.
@@ -725,7 +734,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$set: {[metaField]: "a"}},
multi: true
}],
- resultDocList: [doc1, {_id: 6, [timeField]: dateTime, [metaField]: "a", a: 1}],
+ resultDocList: [doc1, {_id: 6, [timeField]: dateTime1, [metaField]: "a", a: 1}],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -759,7 +768,7 @@ function testCaseValidMetaFieldUpdates({testUpdate}) {
u: {$set: {[metaField]: "a"}},
multi: true
}],
- resultDocList: [{_id: 1, [timeField]: dateTime, [metaField]: "a"}, doc2, doc3],
+ resultDocList: [{_id: 1, [timeField]: dateTime1, [metaField]: "a"}, doc2, doc3],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -801,9 +810,9 @@ function testCaseUpdateWithLetDoc({testUpdate}) {
}],
letDoc: {oldVal: "A"},
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: "aaa"},
- {_id: 4, [timeField]: dateTime, [metaField]: "aaa", f: "F"},
- {_id: 5, [timeField]: dateTime, [metaField]: "aaa"}
+ {_id: 1, [timeField]: dateTime1, [metaField]: "aaa"},
+ {_id: 4, [timeField]: dateTime1, [metaField]: "aaa", f: "F"},
+ {_id: 5, [timeField]: dateTime1, [metaField]: "aaa"}
],
n: 3,
pathToMetaFieldBeingUpdated: "",
@@ -820,7 +829,7 @@ function testCaseUpdateWithLetDoc({testUpdate}) {
multi: true,
}],
letDoc: {myVar: "aaa"},
- resultDocList: [{_id: 1, [timeField]: dateTime, [metaField]: "$$myVar"}],
+ resultDocList: [{_id: 1, [timeField]: dateTime1, [metaField]: "$$myVar"}],
n: 1,
pathToMetaFieldBeingUpdated: "",
});
@@ -842,9 +851,9 @@ function testCaseUpdateWithLetDoc({testUpdate}) {
],
letDoc: {val1: "A", val2: "aaa"},
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: "bbb"},
- {_id: 4, [timeField]: dateTime, [metaField]: "bbb", f: "F"},
- {_id: 5, [timeField]: dateTime, [metaField]: "bbb"}
+ {_id: 1, [timeField]: dateTime1, [metaField]: "bbb"},
+ {_id: 4, [timeField]: dateTime1, [metaField]: "bbb", f: "F"},
+ {_id: 5, [timeField]: dateTime1, [metaField]: "bbb"}
],
n: 6,
pathToMetaFieldBeingUpdated: "",
@@ -852,9 +861,9 @@ function testCaseUpdateWithLetDoc({testUpdate}) {
}
function testCaseCollationUpdates({testUpdate}) {
- const collationDoc1 = {_id: 1, [timeField]: dateTime, [metaField]: "café"};
- const collationDoc2 = {_id: 2, [timeField]: dateTime, [metaField]: "cafe"};
- const collationDoc3 = {_id: 3, [timeField]: dateTime, [metaField]: "cafE"};
+ const collationDoc1 = {_id: 1, [timeField]: dateTime1, [metaField]: "café"};
+ const collationDoc2 = {_id: 2, [timeField]: dateTime1, [metaField]: "cafe"};
+ const collationDoc3 = {_id: 3, [timeField]: dateTime1, [metaField]: "cafE"};
const initialDocList = [collationDoc1, collationDoc2, collationDoc3];
// Query on the metaField and modify the metaField using collation with strength level 1.
@@ -867,9 +876,9 @@ function testCaseCollationUpdates({testUpdate}) {
collation: {locale: "fr", strength: 1},
}],
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: "Updated"},
- {_id: 2, [timeField]: dateTime, [metaField]: "Updated"},
- {_id: 3, [timeField]: dateTime, [metaField]: "Updated"}
+ {_id: 1, [timeField]: dateTime1, [metaField]: "Updated"},
+ {_id: 2, [timeField]: dateTime1, [metaField]: "Updated"},
+ {_id: 3, [timeField]: dateTime1, [metaField]: "Updated"}
],
n: 3,
pathToMetaFieldBeingUpdated: "",
@@ -887,7 +896,7 @@ function testCaseCollationUpdates({testUpdate}) {
}],
resultDocList: [
collationDoc1,
- {_id: 2, [timeField]: dateTime, [metaField]: "Updated"},
+ {_id: 2, [timeField]: dateTime1, [metaField]: "Updated"},
collationDoc3,
],
n: 1,
@@ -897,9 +906,9 @@ function testCaseCollationUpdates({testUpdate}) {
function testCaseNullUpdates({testUpdate}) {
// Assumes shard key is meta.a.
- const nullDoc = {_id: 1, [timeField]: dateTime, [metaField]: {a: null, b: 1}};
- const missingDoc1 = {_id: 2, [timeField]: dateTime, [metaField]: {b: 1}};
- const missingDoc2 = {_id: 3, [timeField]: dateTime, [metaField]: "foo"};
+ const nullDoc = {_id: 1, [timeField]: dateTime1, [metaField]: {a: null, b: 1}};
+ const missingDoc1 = {_id: 2, [timeField]: dateTime1, [metaField]: {b: 1}};
+ const missingDoc2 = {_id: 3, [timeField]: dateTime1, [metaField]: "foo"};
const initialDocList = [nullDoc, missingDoc1, missingDoc2];
// Query on the metaField and modify the metaField using collation with strength level 1.
@@ -911,9 +920,9 @@ function testCaseNullUpdates({testUpdate}) {
multi: true,
}],
resultDocList: [
- {_id: 1, [timeField]: dateTime, [metaField]: "Updated"},
- {_id: 2, [timeField]: dateTime, [metaField]: "Updated"},
- {_id: 3, [timeField]: dateTime, [metaField]: "Updated"},
+ {_id: 1, [timeField]: dateTime1, [metaField]: "Updated"},
+ {_id: 2, [timeField]: dateTime1, [metaField]: "Updated"},
+ {_id: 3, [timeField]: dateTime1, [metaField]: "Updated"},
],
n: 3,
});
diff --git a/jstests/sharding/top_chunk_autosplit.js b/jstests/sharding/top_chunk_autosplit.js
index 7d969aed0c8..f189db2e644 100644
--- a/jstests/sharding/top_chunk_autosplit.js
+++ b/jstests/sharding/top_chunk_autosplit.js
@@ -3,8 +3,11 @@
* of 5MB across all sharding tests in wiredTiger.
* @tags: [resource_intensive]
*/
+(function() {
+'use strict';
load('jstests/sharding/autosplit_include.js');
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js"); // for FeatureFlagUtil.isEnabled
function shardSetup(shardConfig, dbName, collName) {
var st = new ShardingTest(shardConfig);
@@ -64,14 +67,14 @@ function runTest(test) {
// Add tags to each shard
var tags = test.shards[i].tags || [];
for (j = 0; j < tags.length; j++) {
- sh.addShardTag(test.shards[i].name, tags[j]);
+ st.addShardTag(test.shards[i].name, tags[j]);
}
}
// Add tag ranges associated to a tag
var tagRanges = test.tagRanges || [];
for (var j = 0; j < tagRanges.length; j++) {
- sh.addTagRange(db + "." + collName,
+ st.addTagRange(db + "." + collName,
{x: tagRanges[j].range.min},
{x: tagRanges[j].range.max},
tagRanges[j].tag);
@@ -105,7 +108,7 @@ function runTest(test) {
for (var i = 0; i < test.shards.length; i++) {
var tags = test.shards[i].tags || [];
for (j = 0; j < tags.length; j++) {
- sh.removeShardTag(test.shards[i].name, tags[j]);
+ st.removeShardTag(test.shards[i].name, tags[j]);
}
}
@@ -137,6 +140,16 @@ var collName = "coll";
var st = shardSetup(
{name: "topchunk", shards: 4, chunkSize: 1, other: {enableAutoSplit: true}}, dbName, collName);
+
+// TODO SERVER-66652 remove this test after 7.0 branches out
+const noMoreAutoSplitterFeatureFlag =
+ FeatureFlagUtil.isEnabled(st.configRS.getPrimary().getDB('admin'), "NoMoreAutoSplitter");
+if (noMoreAutoSplitterFeatureFlag) {
+ jsTestLog("Skipping as featureFlagNoMoreAutosplitter is enabled");
+ st.stop();
+ return;
+}
+
var db = st.getDB(dbName);
var coll = db[collName];
var configDB = st.s.getDB('config');
@@ -380,3 +393,4 @@ if (unsupported.indexOf(st.rs0.getPrimary().adminCommand({serverStatus: 1}).stor
}
st.stop();
+})();
diff --git a/jstests/sharding/transfer_mods_large_batches.js b/jstests/sharding/transfer_mods_large_batches.js
new file mode 100644
index 00000000000..966285024ed
--- /dev/null
+++ b/jstests/sharding/transfer_mods_large_batches.js
@@ -0,0 +1,132 @@
+/**
+ * Verify the recipient shard continues to run the _transferMods command against the donor shard
+ * primary until it receives an empty _transferMods batch after the kCommitStart recipient state was
+ * reached. In particular, a batch of changes unrelated to the chunk migration must not cause the
+ * recipient shard to stop running the _transferMods command.
+ *
+ * @tags: [uses_transactions]
+ */
+(function() {
+"use strict";
+
+load('jstests/libs/chunk_manipulation_util.js');
+load('jstests/libs/fail_point_util.js');
+load('jstests/sharding/libs/create_sharded_collection_util.js');
+load('jstests/sharding/libs/sharded_transactions_helpers.js');
+
+const staticMongod = MongoRunner.runMongod({}); // Mongod used for startParallelOps().
+const st = new ShardingTest({shards: {rs0: {nodes: 1}, rs1: {nodes: 1}}});
+
+const dbName = "test";
+const collName = "transfer_mods_large_batches";
+const collection = st.s.getDB(dbName).getCollection(collName);
+
+CreateShardedCollectionUtil.shardCollectionWithChunks(collection, {x: 1}, [
+ {min: {x: MinKey}, max: {x: 0}, shard: st.shard0.shardName},
+ {min: {x: 0}, max: {x: 1000}, shard: st.shard0.shardName},
+ {min: {x: 1000}, max: {x: MaxKey}, shard: st.shard1.shardName},
+]);
+
+function insertLargeDocsInTransaction(collection, docIds, shardKey) {
+ const lsid = {id: UUID()};
+ const txnNumber = 0;
+ const largeStr = "x".repeat(9 * 1024 * 1024);
+
+ for (let i = 0; i < docIds.length; ++i) {
+ const docToInsert = {_id: docIds[i]._id};
+ Object.assign(docToInsert, shardKey);
+ docToInsert.note = "large document to force separate _transferMods call";
+ docToInsert.padding = largeStr;
+
+ const commandObj = {
+ documents: [docToInsert],
+ lsid: lsid,
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+ };
+
+ if (i === 0) {
+ commandObj.startTransaction = true;
+ }
+
+ assert.commandWorked(collection.runCommand("insert", commandObj));
+ }
+
+ assert.commandWorked(collection.getDB().adminCommand(
+ {commitTransaction: 1, lsid: lsid, txnNumber: NumberLong(txnNumber), autocommit: false}));
+}
+
+assert.commandWorked(collection.insert([
+ {_id: 1, x: -2, note: "keep out of chunk range being migrated"},
+ {_id: 2, x: 100, note: "keep in chunk range being migrated"},
+]));
+
+pauseMoveChunkAtStep(st.shard0, moveChunkStepNames.reachedSteadyState);
+const fp = configureFailPoint(st.shard1.rs.getPrimary(), "migrateThreadHangAfterSteadyTransition");
+
+const joinMoveChunk = moveChunkParallel(
+ staticMongod, st.s.host, {x: 1}, undefined, collection.getFullName(), st.shard1.shardName);
+
+waitForMoveChunkStep(st.shard0, moveChunkStepNames.reachedSteadyState);
+insertLargeDocsInTransaction(collection, [{_id: 3}, {_id: 4}], {x: -1000});
+assert.commandWorked(
+ collection.insert({_id: 5, x: 1, note: "inserted into range after large _transferMods"}));
+
+unpauseMoveChunkAtStep(st.shard0, moveChunkStepNames.reachedSteadyState);
+
+// The kCommitStart state isn't a separate "step" of the chunk migration procedure on the
+// recipient shard. We therefore cannot use the waitForMigrateStep() helper to wait for the
+// _recvChunkCommit command to have been received by the recipient shard. The problematic
+// behavior of the recipient shard finishing its catch up too early only manifests after the
+// _recvChunkCommit command has been received by the recipient shard.
+assert.soon(() => {
+ const res = assert.commandWorked(fp.conn.adminCommand({_recvChunkStatus: 1}));
+ return res.state !== "steady";
+});
+
+fp.off();
+joinMoveChunk();
+
+class ArrayCursor {
+ constructor(arr) {
+ this.i = 0;
+ this.arr = arr;
+ }
+
+ hasNext() {
+ return this.i < this.arr.length;
+ }
+
+ next() {
+ return this.arr[this.i++];
+ }
+}
+
+const expected = new ArrayCursor([
+ {_id: 1, x: -2, note: "keep out of chunk range being migrated"},
+ {_id: 2, x: 100, note: "keep in chunk range being migrated"},
+ {_id: 3, x: -1000, note: "large document to force separate _transferMods call"},
+ {_id: 4, x: -1000, note: "large document to force separate _transferMods call"},
+ {_id: 5, x: 1, note: "inserted into range after large _transferMods"},
+]);
+
+const diff = ((diff) => {
+ return {
+ docsWithDifferentContents: diff.docsWithDifferentContents.map(
+ ({first, second}) => ({expected: first, actual: second})),
+ docsExtraAfterMigration: diff.docsMissingOnFirst,
+ docsMissingAfterMigration: diff.docsMissingOnSecond,
+ };
+})(
+ DataConsistencyChecker.getDiff(
+ expected, collection.find({}, {_id: 1, x: 1, note: 1}).sort({_id: 1, x: 1})));
+
+assert.eq(diff, {
+ docsWithDifferentContents: [],
+ docsExtraAfterMigration: [],
+ docsMissingAfterMigration: [],
+});
+
+st.stop();
+MongoRunner.stopMongod(staticMongod);
+})();
diff --git a/jstests/sharding/ttl_deletes_not_targeting_orphaned_documents.js b/jstests/sharding/ttl_deletes_not_targeting_orphaned_documents.js
index 9fb36baeecc..22bf8338a30 100644
--- a/jstests/sharding/ttl_deletes_not_targeting_orphaned_documents.js
+++ b/jstests/sharding/ttl_deletes_not_targeting_orphaned_documents.js
@@ -41,11 +41,10 @@ assert.commandWorked(bulk.execute());
assert.commandWorked(
st.s.adminCommand({moveChunk: collName, find: {_id: 0}, to: st.shard1.shardName}));
-// Optimistically wait 15 seconds (10 seconds TTL index delay followed by 5 rounds of TTL monitor)
-sleep(15000);
-
// Verify that TTL index worked properly on owned documents
-assert.eq(coll.countDocuments({}), 0);
+assert.soon(function() {
+ return coll.countDocuments({}) == 0;
+}, "Failed to move all documents", 60000 /* 60 seconds */, 5000 /* 5 seconds */);
// Verify that TTL index did not delete orphaned documents
assert.eq(nDocs, st.rs0.getPrimary().getCollection(collName).countDocuments({}));
diff --git a/jstests/sharding/update_delete_many_metrics.js b/jstests/sharding/update_delete_many_metrics.js
new file mode 100644
index 00000000000..3987f9ab156
--- /dev/null
+++ b/jstests/sharding/update_delete_many_metrics.js
@@ -0,0 +1,163 @@
+/**
+ * Tests for the 'metrics.query' section of the mongos and mongod serverStatus response verifying
+ * counters for updateMany and deleteMany
+ * @tags: [multiversion_incompatible]
+ */
+
+(function() {
+"use strict";
+
+{
+ const st = new ShardingTest({shards: 2, rs: {nodes: 2}});
+ const mongodConns = [];
+ st.rs0.nodes.forEach(node => mongodConns.push(node));
+ st.rs1.nodes.forEach(node => mongodConns.push(node));
+
+ const testDB = st.s.getDB("test");
+ const shardedColl = testDB.shardedColl;
+ const unshardedColl = testDB.unshardedColl;
+
+ assert.commandWorked(st.s0.adminCommand({enableSharding: testDB.getName()}));
+ st.ensurePrimaryShard(testDB.getName(), st.shard0.shardName);
+
+ // Shard shardedColl on {x:1}, split it at {x:0}, and move chunk {x:1} to shard1.
+ st.shardColl(shardedColl, {x: 1}, {x: 0}, {x: 1});
+
+ // Insert one document on each shard.
+ assert.commandWorked(shardedColl.insert({x: 1, _id: 1}));
+ assert.commandWorked(shardedColl.insert({x: 2, _id: 2}));
+ assert.commandWorked(shardedColl.insert({x: -1, _id: -1}));
+ assert.commandWorked(shardedColl.insert({x: -2, _id: -2}));
+ assert.eq(4, shardedColl.find().itcount());
+
+ assert.commandWorked(unshardedColl.insert({x: 1, _id: 1}));
+ assert.commandWorked(unshardedColl.insert({x: -11, _id: 0}));
+ assert.eq(2, unshardedColl.find().itcount());
+
+ let mongosServerStatus = testDB.adminCommand({serverStatus: 1});
+
+ // Verification for initial values.
+ assert.eq(0, mongosServerStatus.metrics.query.updateManyCount);
+ assert.eq(0, mongosServerStatus.metrics.query.deleteManyCount);
+
+ assert.commandWorked(unshardedColl.update({_id: 1}, {$set: {a: 2}}, {multi: false}));
+ assert.eq(1, unshardedColl.find({a: 2}).count());
+
+ assert.commandWorked(shardedColl.update({_id: 1}, {$set: {a: 2}}, {multi: false}));
+ assert.eq(1, shardedColl.find({a: 2}).count());
+
+ // 3 update with multi:true calls.
+ assert.commandWorked(unshardedColl.update({}, {$set: {a: 3}}, {multi: true}));
+ assert.eq(2, unshardedColl.find({a: 3}).count());
+ assert.commandWorked(shardedColl.update({}, {$set: {a: 3}}, {multi: true}));
+ assert.eq(0, shardedColl.find({a: 2}).count());
+ assert.eq(4, shardedColl.find({a: 3}).count());
+ assert.commandWorked(shardedColl.update({}, {$set: {a: 4}}, {multi: true}));
+ assert.eq(4, shardedColl.find({a: 4}).count());
+
+ // 2 updateMany calls.
+ assert.commandWorked(shardedColl.updateMany({}, {$set: {array: 'string', doc: 'string'}}));
+ assert.commandWorked(unshardedColl.updateMany({}, {$set: {array: 'string', doc: 'string'}}));
+
+ // batch update: 2 more, so 7 updates in total
+ var request = {
+ update: shardedColl.getName(),
+ updates: [{q: {}, u: {$set: {c: 3}}, multi: true}, {q: {}, u: {$set: {a: 5}}, multi: true}],
+ writeConcern: {w: 1},
+ ordered: false
+ };
+ shardedColl.runCommand(request);
+ assert.eq(4, shardedColl.find({a: 5}).count());
+
+ // Use deleteMany to delete one of the documents.
+ const result = shardedColl.deleteMany({_id: 1});
+ assert.commandWorked(result);
+ assert.eq(1, result.deletedCount);
+ assert.eq(3, shardedColl.find().itcount());
+ // Next call will not increase count.
+ assert.commandWorked(shardedColl.deleteOne({_id: 1}));
+
+ // Use deleteMany to delete one document in the unsharded collection.
+ assert.commandWorked(unshardedColl.deleteMany({_id: 1}));
+ // Next call will not increase count.
+ assert.commandWorked(unshardedColl.deleteOne({_id: 1}));
+
+ mongosServerStatus = testDB.adminCommand({serverStatus: 1});
+ // Verification for metrics.
+ assert.eq(7, mongosServerStatus.metrics.query.updateManyCount);
+ assert.eq(2, mongosServerStatus.metrics.query.deleteManyCount);
+
+ st.stop();
+}
+
+{
+ const rst = new ReplSetTest({nodes: 2});
+ rst.startSet();
+ rst.initiate();
+
+ const primary = rst.getPrimary();
+ const testDB = primary.getDB("test");
+ const testColl = testDB.coll;
+
+ // Insert documents
+ assert.commandWorked(testColl.insert({x: 1, _id: 1}));
+ assert.commandWorked(testColl.insert({x: 2, _id: 2}));
+ assert.commandWorked(testColl.insert({x: -1, _id: -1}));
+ assert.commandWorked(testColl.insert({x: -2, _id: -2}));
+ assert.eq(4, testColl.find().itcount());
+
+ let mongosServerStatus = testDB.adminCommand({serverStatus: 1});
+
+ // Verification for initial values.
+ assert.eq(0, mongosServerStatus.metrics.query.updateManyCount);
+ assert.eq(0, mongosServerStatus.metrics.query.deleteManyCount);
+ assert.eq(0, mongosServerStatus.metrics.query.updateDeleteManyDocumentsMaxCount);
+ assert.eq(0, mongosServerStatus.metrics.query.updateDeleteManyDurationMaxMs);
+ assert.eq(0, mongosServerStatus.metrics.query.updateDeleteManyDocumentsTotalCount);
+ assert.eq(0, mongosServerStatus.metrics.query.updateDeleteManyDurationTotalMs);
+
+ assert.commandWorked(testColl.update({_id: 1}, {$set: {a: 2}}, {multi: false}));
+ assert.eq(1, testColl.find({a: 2}).count());
+ // 2 update with multi:true calls.
+ assert.commandWorked(testColl.update({}, {$set: {a: 3}}, {multi: true}));
+ assert.eq(0, testColl.find({a: 2}).count());
+ assert.eq(4, testColl.find({a: 3}).count());
+ assert.commandWorked(testColl.update({}, {$set: {a: 4}}, {multi: true}));
+ assert.eq(4, testColl.find({a: 4}).count());
+
+ // 1 updateMany call.
+ assert.commandWorked(testColl.updateMany({}, {$set: {array: 'string', doc: 'string'}}));
+
+ // batch update: 2 more, so 5 updates in total
+ var request = {
+ update: testColl.getName(),
+ updates: [{q: {}, u: {$set: {c: 3}}, multi: true}, {q: {}, u: {$set: {a: 5}}, multi: true}],
+ writeConcern: {w: 1},
+ ordered: false
+ };
+ testColl.runCommand(request);
+ assert.eq(4, testColl.find({a: 5}).count());
+
+ // Use deleteMany to delete two of the documents.
+ const result = testColl.deleteMany({_id: {$lt: 0}});
+ assert.commandWorked(result);
+ assert.eq(2, result.deletedCount);
+ assert.eq(2, testColl.find().itcount());
+ // Next call will not increase count.
+ assert.commandWorked(testColl.deleteOne({_id: 1}));
+
+ mongosServerStatus = testDB.adminCommand({serverStatus: 1});
+
+ // Verification for final metric values.
+ assert.eq(5, mongosServerStatus.metrics.query.updateManyCount);
+ assert.eq(1, mongosServerStatus.metrics.query.deleteManyCount);
+ assert.eq(4, mongosServerStatus.metrics.query.updateDeleteManyDocumentsMaxCount);
+ assert(mongosServerStatus.metrics.query.hasOwnProperty("updateDeleteManyDurationMaxMs"));
+ assert.lte(0, mongosServerStatus.metrics.query.updateDeleteManyDurationMaxMs);
+ assert.eq(22, mongosServerStatus.metrics.query.updateDeleteManyDocumentsTotalCount);
+ assert(mongosServerStatus.metrics.query.hasOwnProperty("updateDeleteManyDurationTotalMs"));
+ assert.lte(0, mongosServerStatus.metrics.query.updateDeleteManyDurationTotalMs);
+
+ rst.stopSet();
+}
+})();
diff --git a/jstests/sharding/write_cmd_auto_split.js b/jstests/sharding/write_cmd_auto_split.js
index 9d7d55a5729..c98103a468e 100644
--- a/jstests/sharding/write_cmd_auto_split.js
+++ b/jstests/sharding/write_cmd_auto_split.js
@@ -9,9 +9,19 @@
'use strict';
load('jstests/sharding/autosplit_include.js');
load("jstests/sharding/libs/find_chunks_util.js");
+load("jstests/libs/feature_flag_util.js"); // for FeatureFlagUtil.isEnabled
var st = new ShardingTest({shards: 1, other: {chunkSize: 1, enableAutoSplit: true}});
+// TODO SERVER-66652 remove this test after 7.0 branches out
+const noMoreAutoSplitterFeatureFlag =
+ FeatureFlagUtil.isEnabled(st.configRS.getPrimary().getDB('admin'), "NoMoreAutoSplitter");
+if (noMoreAutoSplitterFeatureFlag) {
+ jsTestLog("Skipping as featureFlagNoMoreAutosplitter is enabled");
+ st.stop();
+ return;
+}
+
var configDB = st.s.getDB('config');
var doc1k = (new Array(1024)).join('x');
diff --git a/jstests/sharding/zone_changes_hashed.js b/jstests/sharding/zone_changes_hashed.js
index 83265fac92f..5d9323c6f5d 100644
--- a/jstests/sharding/zone_changes_hashed.js
+++ b/jstests/sharding/zone_changes_hashed.js
@@ -1,9 +1,14 @@
/**
* Test that chunks and documents are moved correctly after zone changes.
+ *
+ * * @tags: [
+ * requires_fcv_60,
+ * ]
*/
(function() {
'use strict';
+load("jstests/libs/feature_flag_util.js");
load("jstests/sharding/libs/zone_changes_util.js");
load("jstests/sharding/libs/find_chunks_util.js");
@@ -47,7 +52,7 @@ function findHighestChunkBounds(chunkBounds) {
return highestBounds;
}
-let st = new ShardingTest({shards: 3});
+const st = new ShardingTest({shards: 3, other: {chunkSize: 1, enableAutoSplitter: false}});
let primaryShard = st.shard0;
let dbName = "test";
let testDB = st.s.getDB(dbName);
@@ -56,8 +61,8 @@ let coll = testDB.hashed;
let ns = coll.getFullName();
let shardKey = {x: "hashed"};
-assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
-st.ensurePrimaryShard(dbName, primaryShard.shardName);
+assert.commandWorked(
+ st.s.adminCommand({enableSharding: dbName, primaryShard: primaryShard.shardName}));
jsTest.log(
"Shard the collection. The command creates two chunks on each of the shards by default.");
@@ -66,7 +71,15 @@ let chunkDocs = findChunksUtil.findChunksByNs(configDB, ns).sort({min: 1}).toArr
let shardChunkBounds = chunkBoundsUtil.findShardChunkBounds(chunkDocs);
jsTest.log("Insert docs (one for each chunk) and check that they end up on the right shards.");
-let docs = [{x: -25}, {x: -18}, {x: -5}, {x: -1}, {x: 5}, {x: 10}];
+const bigString = 'X'.repeat(1024 * 1024); // 1MB
+let docs = [
+ {x: -25, s: bigString},
+ {x: -18, s: bigString},
+ {x: -5, s: bigString},
+ {x: -1, s: bigString},
+ {x: 5, s: bigString},
+ {x: 10, s: bigString}
+];
assert.commandWorked(coll.insert(docs));
let docChunkBounds = [];
@@ -126,7 +139,10 @@ shardTags = {
};
assertShardTags(configDB, shardTags);
-let numChunksToMove = zoneChunkBounds["zoneB"].length / 2;
+const balanceAccordingToDataSize = FeatureFlagUtil.isEnabled(
+ st.configRS.getPrimary().getDB('admin'), "BalanceAccordingToDataSize");
+let numChunksToMove = balanceAccordingToDataSize ? zoneChunkBounds["zoneB"].length - 1
+ : zoneChunkBounds["zoneB"].length / 2;
runBalancer(st, numChunksToMove);
shardChunkBounds = {
[st.shard0.shardName]: zoneChunkBounds["zoneB"].slice(0, numChunksToMove),
diff --git a/jstests/sharding/zone_changes_range.js b/jstests/sharding/zone_changes_range.js
index 2f2963da220..78ec7c40dad 100644
--- a/jstests/sharding/zone_changes_range.js
+++ b/jstests/sharding/zone_changes_range.js
@@ -7,7 +7,7 @@
load("jstests/sharding/libs/zone_changes_util.js");
load("jstests/sharding/libs/find_chunks_util.js");
-let st = new ShardingTest({shards: 3});
+const st = new ShardingTest({shards: 3, other: {chunkSize: 1, enableAutoSplitter: false}});
let primaryShard = st.shard0;
let dbName = "test";
let testDB = st.s.getDB(dbName);
@@ -16,8 +16,8 @@ let coll = testDB.range;
let ns = coll.getFullName();
let shardKey = {x: 1};
-assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
-st.ensurePrimaryShard(dbName, primaryShard.shardName);
+assert.commandWorked(
+ st.s.adminCommand({enableSharding: dbName, primaryShard: primaryShard.shardName}));
jsTest.log("Shard the collection and create chunks.");
assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: shardKey}));
@@ -26,8 +26,15 @@ assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: 0}}));
assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: 10}}));
assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: 20}}));
+const bigString = 'X'.repeat(1024 * 1024); // 1MB
jsTest.log("Insert docs (one for each chunk) and check that they end up on the primary shard.");
-let docs = [{x: -15}, {x: -5}, {x: 5}, {x: 15}, {x: 25}];
+let docs = [
+ {x: -15, s: bigString},
+ {x: -5, s: bigString},
+ {x: 5, s: bigString},
+ {x: 15, s: bigString},
+ {x: 25, s: bigString}
+];
assert.eq(docs.length, findChunksUtil.countChunksForNs(configDB, ns));
assert.commandWorked(coll.insert(docs));
assert.eq(docs.length, primaryShard.getCollection(ns).count());