summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVesselina Ratcheva <vesselina.ratcheva@10gen.com>2020-01-23 15:57:11 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-02-13 22:24:40 +0000
commitea7294598dc4621245739da201c0aeaf11aaf957 (patch)
tree68081d188ed83dd6cf767c2946b11529d1bc1f7a
parent81d78ae4ff9ea93a5b6ec6b3134557310d89d64d (diff)
downloadmongo-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
-rw-r--r--jstests/hooks/validate_collections.js6
-rw-r--r--jstests/libs/all_commands_test.js72
-rw-r--r--jstests/replsets/db_reads_while_recovering_all_commands.js351
-rw-r--r--jstests/replsets/libs/rollback_test_deluxe.js8
-rw-r--r--jstests/replsets/maintenance2.js5
-rw-r--r--jstests/replsets/maintenance_non-blocking.js3
-rw-r--r--src/mongo/db/commands/dbcheck.cpp4
-rw-r--r--src/mongo/db/commands/dbcommands.cpp6
-rw-r--r--src/mongo/db/commands/distinct.cpp4
-rw-r--r--src/mongo/db/commands/list_collections.cpp3
-rw-r--r--src/mongo/db/commands/list_databases.cpp3
-rw-r--r--src/mongo/db/commands/list_indexes.cpp3
-rw-r--r--src/mongo/db/commands/pipeline_command.cpp4
-rw-r--r--src/mongo/s/commands/cluster_coll_stats_cmd.cpp4
-rw-r--r--src/mongo/s/commands/cluster_db_stats_cmd.cpp4
-rw-r--r--src/mongo/s/commands/cluster_distinct_cmd.cpp4
-rw-r--r--src/mongo/s/commands/cluster_list_databases_cmd.cpp4
-rw-r--r--src/mongo/s/commands/commands_public.cpp8
-rw-r--r--src/mongo/shell/replsettest.js20
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.`);
}