diff options
author | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2020-01-23 15:57:11 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-02-13 22:24:40 +0000 |
commit | ea7294598dc4621245739da201c0aeaf11aaf957 (patch) | |
tree | 68081d188ed83dd6cf767c2946b11529d1bc1f7a | |
parent | 81d78ae4ff9ea93a5b6ec6b3134557310d89d64d (diff) | |
download | mongo-ea7294598dc4621245739da201c0aeaf11aaf957.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
19 files changed, 509 insertions, 7 deletions
diff --git a/jstests/hooks/validate_collections.js b/jstests/hooks/validate_collections.js index b0bc3461ddf..58e1c5bb276 100644 --- a/jstests/hooks/validate_collections.js +++ b/jstests/hooks/validate_collections.js @@ -91,6 +91,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/replsets/db_reads_while_recovering_all_commands.js b/jstests/replsets/db_reads_while_recovering_all_commands.js new file mode 100644 index 00000000000..69729be9d83 --- /dev/null +++ b/jstests/replsets/db_reads_while_recovering_all_commands.js @@ -0,0 +1,351 @@ +/** + * 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}, + _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}, + _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}, + 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}, + 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}, + 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}, + replSetUpdatePosition: {skip: isNotAUserDataRead}, + replSetResizeOplog: {skip: isNotAUserDataRead}, + resetError: {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}, + 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("test"); + 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 920207ee4d7..bbbbcc1fe36 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 ffc2c374c21..497bae360c2 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 42cfdf10cd6..a446095f91b 100644 --- a/src/mongo/db/commands/dbcheck.cpp +++ b/src/mongo/db/commands/dbcheck.cpp @@ -492,6 +492,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 e21d689045a..a7015ebf7ec 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -562,6 +562,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; } @@ -644,6 +647,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 b4e16653a51..d04cb68ebbc 100644 --- a/src/mongo/db/commands/distinct.cpp +++ b/src/mongo/db/commands/distinct.cpp @@ -76,6 +76,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 d3841cbaed8..7ef36d2e1cf 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 19ebc60b933..9a383f85522 100644 --- a/src/mongo/db/commands/list_databases.cpp +++ b/src/mongo/db/commands/list_databases.cpp @@ -68,6 +68,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 dafefa6f1ee..02dc4b7ead2 100644 --- a/src/mongo/db/commands/list_indexes.cpp +++ b/src/mongo/db/commands/list_indexes.cpp @@ -96,6 +96,9 @@ public: return AllowedOnSecondary::kOptIn; } + bool maintenanceOk() const override { + return false; + } virtual bool adminOnly() const { return false; } diff --git a/src/mongo/db/commands/pipeline_command.cpp b/src/mongo/db/commands/pipeline_command.cpp index a70ae8a51d3..d068dc2812a 100644 --- a/src/mongo/db/commands/pipeline_command.cpp +++ b/src/mongo/db/commands/pipeline_command.cpp @@ -168,7 +168,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 c1c9a24d5f8..b6b78d452df 100644 --- a/src/mongo/s/commands/cluster_coll_stats_cmd.cpp +++ b/src/mongo/s/commands/cluster_coll_stats_cmd.cpp @@ -53,6 +53,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 3408419de95..b8b9e2f90f9 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 bba351c4f58..1b19ddaad90 100644 --- a/src/mongo/s/commands/cluster_distinct_cmd.cpp +++ b/src/mongo/s/commands/cluster_distinct_cmd.cpp @@ -64,6 +64,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 fb5e9ab261a..4222656f498 100644 --- a/src/mongo/s/commands/cluster_list_databases_cmd.cpp +++ b/src/mongo/s/commands/cluster_list_databases_cmd.cpp @@ -55,6 +55,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 bcdeaeb3b02..16f56cd9d24 100644 --- a/src/mongo/s/commands/commands_public.cpp +++ b/src/mongo/s/commands/commands_public.cpp @@ -365,6 +365,10 @@ public: return AllowedOnSecondary::kAlways; } + bool maintenanceOk() const override { + return false; + } + bool adminOnly() const override { return false; } @@ -517,6 +521,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 9d4b6ea5a09..928e1d03945 100644 --- a/src/mongo/shell/replsettest.js +++ b/src/mongo/shell/replsettest.js @@ -2193,10 +2193,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) { @@ -2621,7 +2626,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.`); } |