// // Utilities for testing chunk manipulation: moveChunk, mergeChunks, etc. // load('./jstests/libs/test_background_ops.js'); // // Start a background moveChunk. // staticMongod: Server to use for communication, use // "MongoRunner.runMongod({})" to make one. // mongosURL: Like 'localhost:27017'. // findCriteria: Like { _id: 1 }, passed to moveChunk's "find" option. // bounds: Array of two documents that specify the lower and upper // shard key values of a chunk to move. Specify either the // bounds field or the find field but not both. // ns: Like 'dbName.collectionName'. // toShardId: Like 'shard0001'. // // Returns a join function; call it to wait for moveChunk to complete. // function moveChunkParallel(staticMongod, mongosURL, findCriteria, bounds, ns, toShardId) { assert((findCriteria || bounds) && !(findCriteria && bounds), 'Specify either findCriteria or bounds, but not both.'); function runMoveChunk(mongosURL, findCriteria, bounds, ns, toShardId) { assert(mongosURL && ns && toShardId, 'Missing arguments.'); assert((findCriteria || bounds) && !(findCriteria && bounds), 'Specify either findCriteria or bounds, but not both.'); var mongos = new Mongo(mongosURL), admin = mongos.getDB('admin'), cmd = { moveChunk: ns }; if (findCriteria) { cmd.find = findCriteria; } else { cmd.bounds = bounds; } cmd.to = toShardId; cmd._waitForDelete = true; printjson(cmd); var result = admin.runCommand(cmd); printjson(result); assert(result.ok); } // Return the join function. return startParallelOps( staticMongod, runMoveChunk, [mongosURL, findCriteria, bounds, ns, toShardId]); } // moveChunk starts at step 0 and proceeds to 1 (it has *finished* parsing // options), 2 (it has reloaded config and got distributed lock) and so on. var moveChunkStepNames = { parsedOptions: 1, gotDistLock: 2, startedMoveChunk: 3, // called _recvChunkStart on recipient reachedSteadyState: 4, // recipient reports state is "steady" committed: 5, done: 6 }; function numberToName(names, stepNumber) { for (var name in names) { if (names.hasOwnProperty(name) && names[name] == stepNumber) { return name; } } assert(false); } // // Configure a failpoint to make moveChunk hang at a step. // function pauseMoveChunkAtStep(shardConnection, stepNumber) { configureMoveChunkFailPoint(shardConnection, stepNumber, 'alwaysOn'); } // // Allow moveChunk to proceed past a step. // function unpauseMoveChunkAtStep(shardConnection, stepNumber) { configureMoveChunkFailPoint(shardConnection, stepNumber, 'off'); } function proceedToMoveChunkStep(shardConnection, stepNumber) { jsTest.log('moveChunk proceeding from step "' + numberToName(moveChunkStepNames, stepNumber - 1) + '" to "' + numberToName(moveChunkStepNames, stepNumber) + '".'); pauseMoveChunkAtStep(shardConnection, stepNumber); unpauseMoveChunkAtStep(shardConnection, stepNumber - 1); waitForMoveChunkStep(shardConnection, stepNumber); } function configureMoveChunkFailPoint(shardConnection, stepNumber, mode) { assert.between(migrateStepNames.copiedIndexes, stepNumber, migrateStepNames.done, "incorrect stepNumber", true); var admin = shardConnection.getDB('admin'); admin.runCommand({configureFailPoint: 'moveChunkHangAtStep' + stepNumber, mode: mode}); } // // Wait for moveChunk to reach a step (1 through 6). Assumes only one moveChunk // is in mongos's currentOp. // function waitForMoveChunkStep(shardConnection, stepNumber) { var searchString = 'step ' + stepNumber, admin = shardConnection.getDB('admin'); assert.between(migrateStepNames.copiedIndexes, stepNumber, migrateStepNames.done, "incorrect stepNumber", true); var msg = ('moveChunk on ' + shardConnection.shardName + ' never reached step "' + numberToName(moveChunkStepNames, stepNumber) + '".'); assert.soon(function() { var in_progress = admin.currentOp().inprog; for (var i = 0; i < in_progress.length; ++i) { var op = in_progress[i]; if (op.query && op.query.moveChunk) { return op.msg && op.msg.startsWith(searchString); } } return false; }, msg); } var migrateStepNames = { copiedIndexes: 1, deletedPriorDataInRange: 2, cloned: 3, catchup: 4, // About to enter steady state. steady: 5, done: 6 }; // // Configure a failpoint to make migration thread hang at a step (1 through 5). // function pauseMigrateAtStep(shardConnection, stepNumber) { configureMigrateFailPoint(shardConnection, stepNumber, 'alwaysOn'); } // // Allow _recvChunkStart to proceed past a step. // function unpauseMigrateAtStep(shardConnection, stepNumber) { configureMigrateFailPoint(shardConnection, stepNumber, 'off'); } function proceedToMigrateStep(shardConnection, stepNumber) { jsTest.log('Migration thread proceeding from step "' + numberToName(migrateStepNames, stepNumber - 1) + '" to "' + numberToName(migrateStepNames, stepNumber) + '".'); pauseMigrateAtStep(shardConnection, stepNumber); unpauseMigrateAtStep(shardConnection, stepNumber - 1); waitForMigrateStep(shardConnection, stepNumber); } function configureMigrateFailPoint(shardConnection, stepNumber, mode) { assert.between(migrateStepNames.copiedIndexes, stepNumber, migrateStepNames.done, "incorrect stepNumber", true); var admin = shardConnection.getDB('admin'); admin.runCommand({configureFailPoint: 'migrateThreadHangAtStep' + stepNumber, mode: mode}); } // // Wait for moveChunk to reach a step (1 through 6). // function waitForMigrateStep(shardConnection, stepNumber) { var searchString = 'step ' + stepNumber, admin = shardConnection.getDB('admin'); assert.between(migrateStepNames.copiedIndexes, stepNumber, migrateStepNames.done, "incorrect stepNumber", true); var msg = ('Migrate thread on ' + shardConnection.shardName + ' never reached step "' + numberToName(migrateStepNames, stepNumber) + '".'); assert.soon(function() { // verbose = True so we see the migration thread. var in_progress = admin.currentOp(true).inprog; for (var i = 0; i < in_progress.length; ++i) { var op = in_progress[i]; if (op.desc && op.desc === 'migrateThread') { if (op.hasOwnProperty('msg')) { return op.msg.startsWith(searchString); } else { return false; } } } return false; }, msg); }