// Test mongos implementation of time-limited operations: verify that mongos correctly forwards max // time to shards, and that mongos correctly times out max time sharded getmore operations (which // are run in series on shards). // // Note that mongos does not time out commands or query ops (which remains responsibility of mongod, // pending development of an interrupt framework for mongos). (function() { 'use strict'; var st = new ShardingTest({shards: 2}); var mongos = st.s0; var shards = [st.shard0, st.shard1]; var coll = mongos.getCollection("foo.bar"); var admin = mongos.getDB("admin"); var exceededTimeLimit = 50; // ErrorCodes::ExceededTimeLimit var cursor; var res; // Helper function to configure "maxTimeAlwaysTimeOut" fail point on shards, which forces mongod // to // throw if it receives an operation with a max time. See fail point declaration for complete // description. var configureMaxTimeAlwaysTimeOut = function(mode) { assert.commandWorked(shards[0].getDB("admin").runCommand( {configureFailPoint: "maxTimeAlwaysTimeOut", mode: mode})); assert.commandWorked(shards[1].getDB("admin").runCommand( {configureFailPoint: "maxTimeAlwaysTimeOut", mode: mode})); }; // Helper function to configure "maxTimeAlwaysTimeOut" fail point on shards, which prohibits // mongod // from enforcing time limits. See fail point declaration for complete description. var configureMaxTimeNeverTimeOut = function(mode) { assert.commandWorked(shards[0].getDB("admin").runCommand( {configureFailPoint: "maxTimeNeverTimeOut", mode: mode})); assert.commandWorked(shards[1].getDB("admin").runCommand( {configureFailPoint: "maxTimeNeverTimeOut", mode: mode})); }; // // Pre-split collection: shard 0 takes {_id: {$lt: 0}}, shard 1 takes {_id: {$gte: 0}}. // assert.commandWorked(admin.runCommand({enableSharding: coll.getDB().getName()})); st.ensurePrimaryShard(coll.getDB().toString(), "shard0000"); assert.commandWorked(admin.runCommand({shardCollection: coll.getFullName(), key: {_id: 1}})); assert.commandWorked(admin.runCommand({split: coll.getFullName(), middle: {_id: 0}})); assert.commandWorked( admin.runCommand({moveChunk: coll.getFullName(), find: {_id: 0}, to: "shard0001"})); // // Insert 100 documents into sharded collection, such that each shard owns 50. // var bulk = coll.initializeUnorderedBulkOp(); for (var i = -50; i < 50; i++) { bulk.insert({_id: i}); } assert.writeOK(bulk.execute()); assert.eq(50, shards[0].getCollection(coll.getFullName()).count()); assert.eq(50, shards[1].getCollection(coll.getFullName()).count()); // // Test that mongos correctly forwards max time to shards for sharded queries. Uses // maxTimeAlwaysTimeOut to ensure mongod throws if it receives a max time. // // Positive test. configureMaxTimeAlwaysTimeOut("alwaysOn"); cursor = coll.find(); cursor.maxTimeMS(60 * 1000); assert.throws(function() { cursor.next(); }, [], "expected query to fail in mongod due to maxTimeAlwaysTimeOut fail point"); // Negative test. configureMaxTimeAlwaysTimeOut("off"); cursor = coll.find(); cursor.maxTimeMS(60 * 1000); assert.doesNotThrow(function() { cursor.next(); }, [], "expected query to not hit time limit in mongod"); // // Test that mongos correctly times out max time sharded getmore operations. Uses // maxTimeNeverTimeOut to ensure mongod doesn't enforce a time limit. // // TODO: This is unimplemented. A test for this functionality should be written as // part of the work for SERVER-19410. // configureMaxTimeNeverTimeOut("alwaysOn"); // Positive test. TODO: see above. // Negative test. ~10s operation, with a high (1-day) limit. cursor = coll.find({ $where: function() { sleep(100); return true; } }); cursor.batchSize(2); cursor.maxTimeMS(1000 * 60 * 60 * 24); assert.doesNotThrow(function() { cursor.next(); }, [], "did not expect mongos to time out first batch of query"); assert.doesNotThrow(function() { cursor.itcount(); }, [], "did not expect getmore ops to hit the time limit"); configureMaxTimeNeverTimeOut("off"); // // Test that mongos correctly forwards max time to shards for sharded commands. Uses // maxTimeAlwaysTimeOut to ensure mongod throws if it receives a max time. // // Positive test for "validate". configureMaxTimeAlwaysTimeOut("alwaysOn"); res = coll.runCommand("validate", {maxTimeMS: 60 * 1000}); assert.commandFailed( res, "expected validate to fail in mongod due to maxTimeAlwaysTimeOut fail point"); assert.eq(res["code"], exceededTimeLimit, "expected code " + exceededTimeLimit + " from validate, instead got: " + tojson(res)); // Negative test for "validate". configureMaxTimeAlwaysTimeOut("off"); assert.commandWorked(coll.runCommand("validate", {maxTimeMS: 60 * 1000}), "expected validate to not hit time limit in mongod"); // Positive test for "count". configureMaxTimeAlwaysTimeOut("alwaysOn"); res = coll.runCommand("count", {maxTimeMS: 60 * 1000}); assert.commandFailed(res, "expected count to fail in mongod due to maxTimeAlwaysTimeOut fail point"); assert.eq(res["code"], exceededTimeLimit, "expected code " + exceededTimeLimit + " from count , instead got: " + tojson(res)); // Negative test for "count". configureMaxTimeAlwaysTimeOut("off"); assert.commandWorked(coll.runCommand("count", {maxTimeMS: 60 * 1000}), "expected count to not hit time limit in mongod"); // Positive test for "collStats". configureMaxTimeAlwaysTimeOut("alwaysOn"); res = coll.runCommand("collStats", {maxTimeMS: 60 * 1000}); assert.commandFailed( res, "expected collStats to fail in mongod due to maxTimeAlwaysTimeOut fail point"); assert.eq( res["code"], exceededTimeLimit, "expected code " + exceededTimeLimit + " from collStats, instead got: " + tojson(res)); // Negative test for "collStats". configureMaxTimeAlwaysTimeOut("off"); assert.commandWorked(coll.runCommand("collStats", {maxTimeMS: 60 * 1000}), "expected collStats to not hit time limit in mongod"); // Positive test for "mapReduce". configureMaxTimeAlwaysTimeOut("alwaysOn"); res = coll.runCommand("mapReduce", { map: function() { emit(0, 0); }, reduce: function(key, values) { return 0; }, out: {inline: 1}, maxTimeMS: 60 * 1000 }); assert.commandFailed( res, "expected mapReduce to fail in mongod due to maxTimeAlwaysTimeOut fail point"); assert.eq( res["code"], exceededTimeLimit, "expected code " + exceededTimeLimit + " from mapReduce, instead got: " + tojson(res)); // Negative test for "mapReduce". configureMaxTimeAlwaysTimeOut("off"); assert.commandWorked(coll.runCommand("mapReduce", { map: function() { emit(0, 0); }, reduce: function(key, values) { return 0; }, out: {inline: 1}, maxTimeMS: 60 * 1000 }), "expected mapReduce to not hit time limit in mongod"); // Positive test for "aggregate". configureMaxTimeAlwaysTimeOut("alwaysOn"); res = coll.runCommand("aggregate", {pipeline: [], maxTimeMS: 60 * 1000}); assert.commandFailed( res, "expected aggregate to fail in mongod due to maxTimeAlwaysTimeOut fail point"); assert.eq( res["code"], exceededTimeLimit, "expected code " + exceededTimeLimit + " from aggregate , instead got: " + tojson(res)); // Negative test for "aggregate". configureMaxTimeAlwaysTimeOut("off"); assert.commandWorked(coll.runCommand("aggregate", {pipeline: [], maxTimeMS: 60 * 1000}), "expected aggregate to not hit time limit in mongod"); // Positive test for "moveChunk". configureMaxTimeAlwaysTimeOut("alwaysOn"); res = admin.runCommand({ moveChunk: coll.getFullName(), find: {_id: 0}, to: "shard0000", maxTimeMS: 1000 * 60 * 60 * 24 }); assert.commandFailed( res, "expected moveChunk to fail in mongod due to maxTimeAlwaysTimeOut fail point"); assert.eq( res["code"], exceededTimeLimit, "expected code " + exceededTimeLimit + " from moveChunk, instead got: " + tojson(res)); // Negative test for "moveChunk". configureMaxTimeAlwaysTimeOut("off"); assert.commandWorked(admin.runCommand({ moveChunk: coll.getFullName(), find: {_id: 0}, to: "shard0000", maxTimeMS: 1000 * 60 * 60 * 24 }), "expected moveChunk to not hit time limit in mongod"); // TODO Test additional commmands. st.stop(); })();