diff options
author | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2020-02-14 16:17:33 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-06-26 02:45:35 +0000 |
commit | 21184efa95bdb654726b9ad516621965d2581976 (patch) | |
tree | 16e4403e5e732ebbbf163e8e878d503124070e85 | |
parent | f43c336b61fcf493510080807937870db4ed75ba (diff) | |
download | mongo-21184efa95bdb654726b9ad516621965d2581976.tar.gz |
SERVER-45610 Reject commands that read data when node is in RECOVERING state
create mode 100644 jstests/libs/all_commands_test.js
create mode 100644 jstests/replsets/db_reads_while_recovering_all_commands.js
(cherry picked from commit 34dc015fcb40b8e4c2c99aadf1a78d7b64de6146)
23 files changed, 526 insertions, 9 deletions
diff --git a/jstests/hooks/validate_collections.js b/jstests/hooks/validate_collections.js index a5aa67eb72e..5ded2e637c0 100644 --- a/jstests/hooks/validate_collections.js +++ b/jstests/hooks/validate_collections.js @@ -94,6 +94,12 @@ function CollectionValidator() { conn.setSlaveOk(); jsTest.authenticate(conn); + // Skip validating collections for arbiters. + if (conn.getDB('admin').isMaster('admin').arbiterOnly === true) { + print('Skipping collection validation on arbiter ' + host); + return {ok: 1}; + } + const requiredFCV = jsTest.options().forceValidationWithFeatureCompatibilityVersion; if (requiredFCV) { // Make sure this node has the desired FCV as it may take time for the updates to diff --git a/jstests/libs/all_commands_test.js b/jstests/libs/all_commands_test.js new file mode 100644 index 00000000000..c3d02fbd548 --- /dev/null +++ b/jstests/libs/all_commands_test.js @@ -0,0 +1,72 @@ +/** + * A library for testing behaviors over the set of all available commands. + * Users of this library must declare the expected results for all commands and pass + * them into a cmdMap object. + * + * Each entry in the map should have at least the following fields: + * { + * command: {<command object, e.g. 'find: test, filter: {a: 1}'>} + * skip: <reason string> // optional field + * } + * + * All the logic about how exactly a test should run is defined by the user. + * See the 'testAllCommands' function. + */ +const AllCommandsTest = (function() { + "use strict"; + /** + * Verifies that the command map contains an entry for every command that exists on the server. + * This is already called in 'testAllCommands', so there is no need to call this directly. + * + * @param {Object} conn The shell connection to run the suite over. + * @param {map} cmdMap A map of all commands, with their invocations and expected behavior. + */ + function checkCommandCoverage(conn, cmdMap) { + const res = assert.commandWorked(conn.adminCommand({listCommands: 1})); + const commandsInListCommands = Object.keys(res.commands); + let missingCommands = []; + + // Make sure that all valid commands are covered in the cmdMap. + for (const command of commandsInListCommands) { + if (!cmdMap[command]) { + missingCommands.push(command); + } + } + if (missingCommands.length !== 0) { + throw new Error("Command map is missing entries for " + missingCommands); + } + + return commandsInListCommands; + } + + /** + * The runner function for this library. + * Use the 'skip' option for tests that should not run. + * + * @param {Object} conn The shell connection to run the suite over. + * @param {map} cmdMap A map of all commands, with their invocations and expected behavior. + * @param {function} testFn A user-defined function to execute on every command. + */ + function testAllCommands(conn, cmdMap, testFn) { + // First check that the map contains all available commands. + const commands = checkCommandCoverage(conn, cmdMap); + + for (const command of commands) { + const test = cmdMap[command]; + + // Coverage already guaranteed above, but check again just in case. + assert(test, "Coverage failure: must explicitly define a test for " + command); + + if (test.skip !== undefined) { + jsTestLog("Skipping " + command + ": " + test.skip); + continue; + } + + // Run logic specified by caller. + jsTestName("Testing " + command); + testFn(test); + } + } + + return {testAllCommands: testAllCommands}; +})();
\ No newline at end of file diff --git a/jstests/noPassthrough/libs/index_build.js b/jstests/noPassthrough/libs/index_build.js index cfece08bf63..8a2095a4abe 100644 --- a/jstests/noPassthrough/libs/index_build.js +++ b/jstests/noPassthrough/libs/index_build.js @@ -115,7 +115,7 @@ class IndexBuildTest { notReadyIndexes = notReadyIndexes || []; options = options || {}; - let res = coll.runCommand("listIndexes", options); + let res = assert.commandWorked(coll.runCommand("listIndexes", options)); assert.eq(numIndexes, res.cursor.firstBatch.length, 'unexpected number of indexes in collection: ' + tojson(res)); diff --git a/jstests/noPassthrough/timestamp_index_builds.js b/jstests/noPassthrough/timestamp_index_builds.js index 41f5ecfb42c..77de045a63f 100644 --- a/jstests/noPassthrough/timestamp_index_builds.js +++ b/jstests/noPassthrough/timestamp_index_builds.js @@ -26,7 +26,7 @@ const rst = new ReplSetTest({ nodeOptions: {setParameter: {logComponentVerbosity: tojsononeline({storage: {recovery: 2}})}} }); const nodes = rst.startSet(); -rst.initiate(); +rst.initiateWithHighElectionTimeout(); if (!rst.getPrimary().adminCommand("serverStatus").storageEngine.supportsSnapshotReadConcern) { // Only snapshotting storage engines require correct timestamping of index builds. @@ -78,6 +78,7 @@ for (let nodeIdx = 0; nodeIdx < 2; ++nodeIdx) { { jsTestLog("Starting as a replica set. Both indexes should exist. Node: " + nodeIdentity); let conn = rst.start(nodeIdx, {startClean: false}, true); + rst.waitForState(conn, ReplSetTest.State.SECONDARY); conn.setSlaveOk(); assert.eq(2, getColl(conn).getIndexes().length); rst.stop(nodeIdx); diff --git a/jstests/replsets/buildindexes_false_with_system_indexes.js b/jstests/replsets/buildindexes_false_with_system_indexes.js index 2c394d3e264..a1f1c8d6d45 100644 --- a/jstests/replsets/buildindexes_false_with_system_indexes.js +++ b/jstests/replsets/buildindexes_false_with_system_indexes.js @@ -64,6 +64,7 @@ assert.eq(["_id_"], hiddenAdminDb.system.users.getIndexes().map(x => x.name).sor assert.eq(["_id_"], hiddenAdminDb.system.roles.getIndexes().map(x => x.name).sort()); secondary = rst.restart(secondary, {}, true /* wait for node to become healthy */); +rst.awaitSecondaryNodes(); secondaryAdminDb = secondary.getDB("admin"); assert.eq(["_id_"], secondaryAdminDb.system.users.getIndexes().map(x => x.name).sort()); assert.eq(["_id_"], secondaryAdminDb.system.roles.getIndexes().map(x => x.name).sort()); diff --git a/jstests/replsets/db_reads_while_recovering_all_commands.js b/jstests/replsets/db_reads_while_recovering_all_commands.js new file mode 100644 index 00000000000..3f27a6b3941 --- /dev/null +++ b/jstests/replsets/db_reads_while_recovering_all_commands.js @@ -0,0 +1,360 @@ +/** + * This file defines tests for all existing commands and their expected behavior when run against a + * node that is in RECOVERING state. + * + * Tagged as multiversion-incompatible as the list of commands will vary depeding on version. + * @tags: [multiversion_incompatible] + */ + +(function() { +"use strict"; + +// This will verify the completeness of our map and run all tests. +load("jstests/libs/all_commands_test.js"); + +const name = jsTestName(); +const dbName = "alltestsdb"; +const collName = "alltestscoll"; +const fullNs = dbName + "." + collName; + +// Pre-written reasons for skipping a test. +const isAnInternalCommand = "internal command"; +const isNotAUserDataRead = "does not return user data"; +const isPrimaryOnly = "primary only"; + +const allCommands = { + _addShard: {skip: isPrimaryOnly}, + _cloneCatalogData: {skip: isPrimaryOnly}, + _cloneCollectionOptionsFromPrimaryShard: {skip: isPrimaryOnly}, + _configsvrAddShard: {skip: isPrimaryOnly}, + _configsvrAddShardToZone: {skip: isPrimaryOnly}, + _configsvrBalancerCollectionStatus: {skip: isPrimaryOnly}, + _configsvrBalancerStart: {skip: isPrimaryOnly}, + _configsvrBalancerStatus: {skip: isPrimaryOnly}, + _configsvrBalancerStop: {skip: isPrimaryOnly}, + _configsvrClearJumboFlag: {skip: isPrimaryOnly}, + _configsvrCommitChunkMerge: {skip: isPrimaryOnly}, + _configsvrCommitChunkMigration: {skip: isPrimaryOnly}, + _configsvrCommitChunkSplit: {skip: isPrimaryOnly}, + _configsvrCommitMovePrimary: {skip: isPrimaryOnly}, + _configsvrCreateCollection: {skip: isPrimaryOnly}, + _configsvrCreateDatabase: {skip: isPrimaryOnly}, + _configsvrDropCollection: {skip: isPrimaryOnly}, + _configsvrDropDatabase: {skip: isPrimaryOnly}, + _configsvrEnableSharding: {skip: isPrimaryOnly}, + _configsvrEnsureChunkVersionIsGreaterThan: {skip: isPrimaryOnly}, + _configsvrMoveChunk: {skip: isPrimaryOnly}, + _configsvrMovePrimary: {skip: isPrimaryOnly}, + _configsvrRefineCollectionShardKey: {skip: isPrimaryOnly}, + _configsvrRemoveShard: {skip: isPrimaryOnly}, + _configsvrRemoveShardFromZone: {skip: isPrimaryOnly}, + _configsvrShardCollection: {skip: isPrimaryOnly}, + _configsvrUpdateZoneKeyRange: {skip: isPrimaryOnly}, + _flushDatabaseCacheUpdates: {skip: isPrimaryOnly}, + _flushRoutingTableCacheUpdates: {skip: isPrimaryOnly}, + _getNextSessionMods: {skip: isPrimaryOnly}, + _getUserCacheGeneration: {skip: isNotAUserDataRead}, + _hashBSONElement: {skip: isNotAUserDataRead}, + _isSelf: {skip: isNotAUserDataRead}, + _killOperations: {skip: isNotAUserDataRead}, + _mergeAuthzCollections: {skip: isPrimaryOnly}, + _migrateClone: {skip: isPrimaryOnly}, + _movePrimary: {skip: isPrimaryOnly}, + _recvChunkAbort: {skip: isPrimaryOnly}, + _recvChunkCommit: {skip: isPrimaryOnly}, + _recvChunkStart: {skip: isPrimaryOnly}, + _recvChunkStatus: {skip: isPrimaryOnly}, + _shardsvrCloneCatalogData: {skip: isPrimaryOnly}, + _shardsvrMovePrimary: {skip: isPrimaryOnly}, + _shardsvrShardCollection: {skip: isPrimaryOnly}, + _transferMods: {skip: isPrimaryOnly}, + abortTransaction: {skip: isPrimaryOnly}, + aggregate: { + command: {aggregate: collName, pipeline: [{$match: {}}], cursor: {}}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + appendOplogNote: {skip: isPrimaryOnly}, + applyOps: {skip: isPrimaryOnly}, + authenticate: {skip: isNotAUserDataRead}, + availableQueryOptions: {skip: isNotAUserDataRead}, + buildInfo: {skip: isNotAUserDataRead}, + captrunc: {skip: isPrimaryOnly}, + checkShardingIndex: {skip: isPrimaryOnly}, + cleanupOrphaned: {skip: isPrimaryOnly}, + clearLog: {skip: isNotAUserDataRead}, + cloneCollectionAsCapped: {skip: isPrimaryOnly}, + cloneCollection: {skip: isPrimaryOnly}, + collMod: {skip: isPrimaryOnly}, + collStats: { + command: {aggregate: collName, pipeline: [{$collStats: {count: {}}}], cursor: {}}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + commitTransaction: {skip: isPrimaryOnly}, + compact: {skip: isNotAUserDataRead}, + configureFailPoint: {skip: isNotAUserDataRead}, + connPoolStats: {skip: isNotAUserDataRead}, + connPoolSync: {skip: isNotAUserDataRead}, + connectionStatus: {skip: isNotAUserDataRead}, + convertToCapped: {skip: isPrimaryOnly}, + coordinateCommitTransaction: {skip: isNotAUserDataRead}, + count: { + command: {count: collName}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + cpuload: {skip: isNotAUserDataRead}, + create: {skip: isPrimaryOnly}, + createIndexes: {skip: isPrimaryOnly}, + createRole: {skip: isPrimaryOnly}, + createUser: {skip: isPrimaryOnly}, + currentOp: {skip: isNotAUserDataRead}, + dataSize: { + command: {dataSize: fullNs}, + }, + dbCheck: {skip: isPrimaryOnly}, + dbHash: { + command: {dbHash: 1}, + }, + dbStats: { + command: {dbStats: 1}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + delete: {skip: isPrimaryOnly}, + distinct: { + command: {distinct: collName, key: "a"}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + driverOIDTest: {skip: isNotAUserDataRead}, + drop: {skip: isPrimaryOnly}, + dropAllRolesFromDatabase: {skip: isPrimaryOnly}, + dropAllUsersFromDatabase: {skip: isPrimaryOnly}, + dropConnections: {skip: isNotAUserDataRead}, + dropDatabase: {skip: isPrimaryOnly}, + dropIndexes: {skip: isPrimaryOnly}, + dropRole: {skip: isPrimaryOnly}, + dropUser: {skip: isPrimaryOnly}, + echo: {skip: isNotAUserDataRead}, + emptycapped: {skip: isPrimaryOnly}, + endSessions: {skip: isNotAUserDataRead}, + explain: { + command: {count: collName}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + features: {skip: isNotAUserDataRead}, + filemd5: {skip: isNotAUserDataRead}, + find: { + command: {find: collName, filter: {a: 1}}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + findAndModify: {skip: isPrimaryOnly}, + flushRouterConfig: {skip: isNotAUserDataRead}, + fsync: {skip: isNotAUserDataRead}, + fsyncUnlock: {skip: isNotAUserDataRead}, + geoSearch: { + command: { + geoSearch: collName, + search: {}, + near: [-42, 42], + }, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary + }, + getCmdLineOpts: {skip: isNotAUserDataRead}, + getDatabaseVersion: {skip: isNotAUserDataRead}, + getDefaultRWConcern: {skip: isNotAUserDataRead}, + getDiagnosticData: {skip: isNotAUserDataRead}, + getFreeMonitoringStatus: {skip: isNotAUserDataRead}, + getLastError: {skip: isPrimaryOnly}, + getLog: {skip: isNotAUserDataRead}, + getMore: { + command: {getMore: NumberLong(123), collection: collName}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary + }, + getParameter: {skip: isNotAUserDataRead}, + getShardMap: {skip: isNotAUserDataRead}, + getShardVersion: {skip: isPrimaryOnly}, + getnonce: {skip: isNotAUserDataRead}, + godinsert: {skip: isAnInternalCommand}, + grantPrivilegesToRole: {skip: isPrimaryOnly}, + grantRolesToRole: {skip: isPrimaryOnly}, + grantRolesToUser: {skip: isPrimaryOnly}, + hostInfo: {skip: isNotAUserDataRead}, + httpClientRequest: {skip: isNotAUserDataRead}, + insert: {skip: isPrimaryOnly}, + internalRenameIfOptionsAndIndexesMatch: {skip: isAnInternalCommand}, + invalidateUserCache: {skip: isNotAUserDataRead}, + isMaster: {skip: isNotAUserDataRead}, + killAllSessions: {skip: isNotAUserDataRead}, + killAllSessionsByPattern: {skip: isNotAUserDataRead}, + killCursors: {skip: isNotAUserDataRead}, + killOp: {skip: isNotAUserDataRead}, + killSessions: {skip: isNotAUserDataRead}, + listCollections: { + command: {listCollections: 1}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary + }, + listCommands: {command: {listCommands: 1}}, + listDatabases: { + command: {listDatabases: 1}, + isAdminCommand: true, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary + }, + listIndexes: { + command: {listIndexes: collName}, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary + }, + lockInfo: {skip: isPrimaryOnly}, + logApplicationMessage: {skip: isNotAUserDataRead}, + logRotate: {skip: isNotAUserDataRead}, + logout: {skip: isNotAUserDataRead}, + makeSnapshot: {skip: isNotAUserDataRead}, + mapReduce: { + command: { + mapReduce: collName, + map: function() {}, + reduce: function(key, vals) {}, + out: {inline: 1} + }, + expectFailure: true, + expectedErrorCode: ErrorCodes.NotMasterOrSecondary, + }, + "mapreduce.shardedfinish": {skip: isAnInternalCommand}, + mergeChunks: {skip: isPrimaryOnly}, + moveChunk: {skip: isPrimaryOnly}, + ping: {skip: isNotAUserDataRead}, + planCacheClear: {skip: isNotAUserDataRead}, + planCacheClearFilters: {skip: isNotAUserDataRead}, + planCacheListFilters: {skip: isNotAUserDataRead}, + planCacheListPlans: {skip: isNotAUserDataRead}, + planCacheListQueryShapes: {skip: isNotAUserDataRead}, + planCacheSetFilter: {skip: isNotAUserDataRead}, + prepareTransaction: {skip: isPrimaryOnly}, + profile: {skip: isPrimaryOnly}, + reapLogicalSessionCacheNow: {skip: isNotAUserDataRead}, + refreshLogicalSessionCacheNow: {skip: isNotAUserDataRead}, + refreshSessions: {skip: isNotAUserDataRead}, + reIndex: {skip: isNotAUserDataRead}, + renameCollection: {skip: isPrimaryOnly}, + repairDatabase: {skip: isNotAUserDataRead}, + repairCursor: {skip: isNotAUserDataRead}, + replSetAbortPrimaryCatchUp: {skip: isNotAUserDataRead}, + replSetFreeze: {skip: isNotAUserDataRead}, + replSetGetConfig: {skip: isNotAUserDataRead}, + replSetGetRBID: {skip: isNotAUserDataRead}, + replSetGetStatus: {skip: isNotAUserDataRead}, + replSetHeartbeat: {skip: isNotAUserDataRead}, + replSetInitiate: {skip: isNotAUserDataRead}, + replSetMaintenance: {skip: isNotAUserDataRead}, + replSetReconfig: {skip: isNotAUserDataRead}, + replSetRequestVotes: {skip: isNotAUserDataRead}, + replSetStepDown: {skip: isNotAUserDataRead}, + replSetStepUp: {skip: isNotAUserDataRead}, + replSetSyncFrom: {skip: isNotAUserDataRead}, + replSetTest: {skip: isNotAUserDataRead}, + replSetTestEgress: {skip: isNotAUserDataRead}, + replSetUpdatePosition: {skip: isNotAUserDataRead}, + replSetResizeOplog: {skip: isNotAUserDataRead}, + resetError: {skip: isNotAUserDataRead}, + restartCatalog: {skip: isNotAUserDataRead}, + revokePrivilegesFromRole: {skip: isPrimaryOnly}, + revokeRolesFromRole: {skip: isPrimaryOnly}, + revokeRolesFromUser: {skip: isPrimaryOnly}, + rolesInfo: {skip: isPrimaryOnly}, + saslContinue: {skip: isPrimaryOnly}, + saslStart: {skip: isPrimaryOnly}, + serverStatus: {skip: isNotAUserDataRead}, + setCommittedSnapshot: {skip: isNotAUserDataRead}, + setDefaultRWConcern: {skip: isPrimaryOnly}, + setIndexCommitQuorum: {skip: isPrimaryOnly}, + setFeatureCompatibilityVersion: {skip: isPrimaryOnly}, + setFreeMonitoring: {skip: isPrimaryOnly}, + setParameter: {skip: isNotAUserDataRead}, + setShardVersion: {skip: isNotAUserDataRead}, + shardConnPoolStats: {skip: isNotAUserDataRead}, + shardingState: {skip: isNotAUserDataRead}, + shutdown: {skip: isNotAUserDataRead}, + sleep: {skip: isNotAUserDataRead}, + splitChunk: {skip: isPrimaryOnly}, + splitVector: {skip: isPrimaryOnly}, + stageDebug: {skip: isPrimaryOnly}, + startRecordingTraffic: {skip: isNotAUserDataRead}, + startSession: {skip: isNotAUserDataRead}, + stopRecordingTraffic: {skip: isNotAUserDataRead}, + top: {skip: isNotAUserDataRead}, + touch: {skip: isNotAUserDataRead}, + unsetSharding: {skip: isNotAUserDataRead}, + update: {skip: isPrimaryOnly}, + updateRole: {skip: isPrimaryOnly}, + updateUser: {skip: isPrimaryOnly}, + usersInfo: {skip: isPrimaryOnly}, + validate: {skip: isNotAUserDataRead}, + voteCommitIndexBuild: {skip: isNotAUserDataRead}, + waitForFailPoint: {skip: isNotAUserDataRead}, + waitForOngoingChunkSplits: {skip: isNotAUserDataRead}, + whatsmysni: {skip: isNotAUserDataRead}, + whatsmyuri: {skip: isNotAUserDataRead} +}; + +/** + * Helper function for failing commands or writes that checks the result 'res' of either. + * If 'code' is null we only check for failure, otherwise we confirm error code matches as + * well. On assert 'msg' is printed. + */ +let assertCommandOrWriteFailed = function(res, code, msg) { + if (res.writeErrors !== undefined) { + assert.neq(0, res.writeErrors.length, msg); + } else if (res.code !== null) { + assert.commandFailedWithCode(res, code, msg); + } else { + assert.commandFailed(res, msg); + } +}; + +// Set up a two-node replica set and put the secondary into RECOVERING state. +const rst = new ReplSetTest({name: name, nodes: [{}, {rsConfig: {priority: 0}}]}); +rst.startSet(); +rst.initiate(); + +const primary = rst.getPrimary(); +const primaryDb = primary.getDB(dbName); +assert.commandWorked(primaryDb.getCollection(collName).insert({a: 42})); +rst.awaitReplication(); + +const secondary = rst.getSecondary(); +const secondaryDb = secondary.getDB(dbName); + +// This will lock the node into RECOVERING state until we turn it off. +assert.commandWorked(secondary.adminCommand({replSetMaintenance: 1})); + +// Run all tests against the RECOVERING node. +AllCommandsTest.testAllCommands(secondary, allCommands, function(test) { + const testDb = secondaryDb.getSiblingDB(dbName); + let cmdDb = testDb; + + if (test.isAdminCommand) { + cmdDb = testDb.getSiblingDB("admin"); + } + + if (test.expectFailure) { + const expectedErrorCode = test.expectedErrorCode; + assertCommandOrWriteFailed( + cmdDb.runCommand(test.command), expectedErrorCode, () => tojson(test.command)); + } else { + assert.commandWorked(cmdDb.runCommand(test.command), () => tojson(test.command)); + } +}); + +// Turn off maintenance mode and stop the test. +assert.commandWorked(secondary.adminCommand({replSetMaintenance: 0})); +rst.stopSet(); +})();
\ No newline at end of file diff --git a/jstests/replsets/libs/rollback_test_deluxe.js b/jstests/replsets/libs/rollback_test_deluxe.js index 0c1d05579b9..fbeed771d1e 100644 --- a/jstests/replsets/libs/rollback_test_deluxe.js +++ b/jstests/replsets/libs/rollback_test_deluxe.js @@ -205,7 +205,13 @@ function RollbackTestDeluxe(name = "FiveNodeDoubleRollbackTest", replSet) { // Wait for collection drops to complete so that we don't get spurious failures during // consistency checks. - rst.nodes.forEach(TwoPhaseDropCollectionTest.waitForAllCollectionDropsToComplete); + rst.nodes.forEach(node => { + if (node.getDB('admin').isMaster('admin').arbiterOnly === true) { + log(`Skipping waiting for collection drops on arbiter ${node.host}`); + return; + } + TwoPhaseDropCollectionTest.waitForAllCollectionDropsToComplete(node); + }); const name = rst.name; // Check collection counts except when unclean shutdowns are allowed, as such a shutdown is diff --git a/jstests/replsets/maintenance2.js b/jstests/replsets/maintenance2.js index a2d2c3f7674..88561dc693e 100644 --- a/jstests/replsets/maintenance2.js +++ b/jstests/replsets/maintenance2.js @@ -34,7 +34,7 @@ assert.eq(2, slaves.length, "Expected 2 slaves but length was " + slaves.length) slaves.forEach(function(slave) { // put slave into maintenance (recovery) mode - slave.getDB("foo").adminCommand({replSetMaintenance: 1}); + assert.commandWorked(slave.getDB("foo").adminCommand({replSetMaintenance: 1})); var stats = slave.getDB("foo").adminCommand({replSetGetStatus: 1}); assert.eq(stats.myState, 3, "Slave should be in recovering state."); @@ -42,6 +42,9 @@ slaves.forEach(function(slave) { print("count should fail in recovering state..."); slave.slaveOk = true; assert.commandFailed(slave.getDB("foo").runCommand({count: "foo"})); + + // unset maintenance mode when done + assert.commandWorked(slave.getDB("foo").adminCommand({replSetMaintenance: 0})); }); // Shut down the set and finish the test. diff --git a/jstests/replsets/maintenance_non-blocking.js b/jstests/replsets/maintenance_non-blocking.js index 5581ffe3546..aa57be0cf64 100644 --- a/jstests/replsets/maintenance_non-blocking.js +++ b/jstests/replsets/maintenance_non-blocking.js @@ -41,6 +41,9 @@ doTest = function() { print("******* fsyncUnlock'n secondary ************* "); sDB.fsyncUnlock(); + + print("******* unset replSetMaintenance on secondary ************* "); + assert.commandWorked(sDB.adminCommand({replSetMaintenance: 0})); replTest.stopSet(); }; diff --git a/src/mongo/db/commands/dbcheck.cpp b/src/mongo/db/commands/dbcheck.cpp index 963ce885d85..6b068c53616 100644 --- a/src/mongo/db/commands/dbcheck.cpp +++ b/src/mongo/db/commands/dbcheck.cpp @@ -502,6 +502,10 @@ public: return AllowedOnSecondary::kNever; } + bool maintenanceOk() const override { + return false; + } + virtual bool adminOnly() const { return false; } diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index 63e72e2a3f0..d9c6fba2042 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -587,6 +587,9 @@ public: AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } virtual bool supportsWriteConcern(const BSONObj& cmd) const override { return false; } @@ -669,6 +672,9 @@ public: AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } virtual bool supportsWriteConcern(const BSONObj& cmd) const override { return false; } diff --git a/src/mongo/db/commands/distinct.cpp b/src/mongo/db/commands/distinct.cpp index 24b1c2518dc..7ec28864bdb 100644 --- a/src/mongo/db/commands/distinct.cpp +++ b/src/mongo/db/commands/distinct.cpp @@ -75,6 +75,10 @@ public: return AllowedOnSecondary::kOptIn; } + bool maintenanceOk() const override { + return false; + } + bool supportsWriteConcern(const BSONObj& cmd) const override { return false; } diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp index 8f7c283e79f..ab63ee10dcf 100644 --- a/src/mongo/db/commands/list_collections.cpp +++ b/src/mongo/db/commands/list_collections.cpp @@ -214,6 +214,9 @@ public: AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { return AllowedOnSecondary::kOptIn; } + bool maintenanceOk() const override { + return false; + } bool adminOnly() const final { return false; } diff --git a/src/mongo/db/commands/list_databases.cpp b/src/mongo/db/commands/list_databases.cpp index 2c34bb715c7..2bd263219d8 100644 --- a/src/mongo/db/commands/list_databases.cpp +++ b/src/mongo/db/commands/list_databases.cpp @@ -67,6 +67,9 @@ public: bool adminOnly() const final { return true; } + bool maintenanceOk() const final { + return false; + } bool supportsWriteConcern(const BSONObj& cmd) const final { return false; } diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp index 6f119e2baf9..ba5123b98e8 100644 --- a/src/mongo/db/commands/list_indexes.cpp +++ b/src/mongo/db/commands/list_indexes.cpp @@ -98,6 +98,9 @@ public: return AllowedOnSecondary::kOptIn; } + bool maintenanceOk() const override { + return false; + } virtual bool adminOnly() const { return false; } diff --git a/src/mongo/db/commands/mr.cpp b/src/mongo/db/commands/mr.cpp index 500e8b733fe..42beb6f6433 100644 --- a/src/mongo/db/commands/mr.cpp +++ b/src/mongo/db/commands/mr.cpp @@ -1429,6 +1429,10 @@ public: return AllowedOnSecondary::kOptIn; } + bool maintenanceOk() const override { + return false; + } + std::size_t reserveBytesForReply() const override { return FindCommon::kInitReplyBufferSize; } diff --git a/src/mongo/db/commands/pipeline_command.cpp b/src/mongo/db/commands/pipeline_command.cpp index 4550b138626..127faa089f1 100644 --- a/src/mongo/db/commands/pipeline_command.cpp +++ b/src/mongo/db/commands/pipeline_command.cpp @@ -152,7 +152,9 @@ public: AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { return AllowedOnSecondary::kOptIn; } - + bool maintenanceOk() const override { + return false; + } ReadWriteType getReadWriteType() const { return ReadWriteType::kRead; } diff --git a/src/mongo/s/commands/cluster_coll_stats_cmd.cpp b/src/mongo/s/commands/cluster_coll_stats_cmd.cpp index c9afc8df754..7e01ccc5d37 100644 --- a/src/mongo/s/commands/cluster_coll_stats_cmd.cpp +++ b/src/mongo/s/commands/cluster_coll_stats_cmd.cpp @@ -52,6 +52,10 @@ public: return false; } + bool maintenanceOk() const override { + return false; + } + std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const override { return CommandHelpers::parseNsCollectionRequired(dbname, cmdObj).ns(); } diff --git a/src/mongo/s/commands/cluster_db_stats_cmd.cpp b/src/mongo/s/commands/cluster_db_stats_cmd.cpp index 752cca26976..08a13178022 100644 --- a/src/mongo/s/commands/cluster_db_stats_cmd.cpp +++ b/src/mongo/s/commands/cluster_db_stats_cmd.cpp @@ -88,6 +88,10 @@ public: return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } + bool adminOnly() const override { return false; } diff --git a/src/mongo/s/commands/cluster_distinct_cmd.cpp b/src/mongo/s/commands/cluster_distinct_cmd.cpp index ff8049b2187..40e02dc4694 100644 --- a/src/mongo/s/commands/cluster_distinct_cmd.cpp +++ b/src/mongo/s/commands/cluster_distinct_cmd.cpp @@ -63,6 +63,10 @@ public: return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } + bool adminOnly() const override { return false; } diff --git a/src/mongo/s/commands/cluster_list_databases_cmd.cpp b/src/mongo/s/commands/cluster_list_databases_cmd.cpp index 9ba0e23bd34..59b0b72c4b4 100644 --- a/src/mongo/s/commands/cluster_list_databases_cmd.cpp +++ b/src/mongo/s/commands/cluster_list_databases_cmd.cpp @@ -54,6 +54,10 @@ public: return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } + bool adminOnly() const override { return true; } diff --git a/src/mongo/s/commands/commands_public.cpp b/src/mongo/s/commands/commands_public.cpp index 9537bc920e9..ca7646bcaf3 100644 --- a/src/mongo/s/commands/commands_public.cpp +++ b/src/mongo/s/commands/commands_public.cpp @@ -333,6 +333,10 @@ public: return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } + bool adminOnly() const override { return false; } @@ -485,6 +489,10 @@ public: return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } + bool adminOnly() const override { return false; } diff --git a/src/mongo/shell/replsettest.js b/src/mongo/shell/replsettest.js index 16e1f553e8f..1867bd51ca7 100644 --- a/src/mongo/shell/replsettest.js +++ b/src/mongo/shell/replsettest.js @@ -1955,10 +1955,15 @@ var ReplSetTest = function(opts) { print("checkDBHashesForReplSet checking data hashes against primary: " + primary.host); - slaves.forEach(secondary => { + slaves.forEach(node => { + // Arbiters have no replicated data. + if (isNodeArbiter(node)) { + print("checkDBHashesForReplSet skipping data of arbiter: " + node.host); + return; + } print("checkDBHashesForReplSet going to check data hashes on secondary: " + - secondary.host); - secondary.getDBNames().forEach(dbName => combinedDBs.add(dbName)); + node.host); + node.getDBNames().forEach(dbName => combinedDBs.add(dbName)); }); for (var dbName of combinedDBs) { @@ -2320,7 +2325,14 @@ var ReplSetTest = function(opts) { } function checkCollectionCountsForReplSet(rst) { - rst.nodes.forEach(node => checkCollectionCountsForNode(node)); + rst.nodes.forEach(node => { + // Arbiters have no replicated collections. + if (isNodeArbiter(node)) { + print("checkCollectionCounts skipping counts for arbiter: " + node.host); + return; + } + checkCollectionCountsForNode(node); + }); assert(success, `Collection counts did not match. search for '${errPrefix}' in logs.`); } |