diff options
-rw-r--r-- | jstests/replsets/awaitable_ismaster.js | 116 | ||||
-rw-r--r-- | src/mongo/db/repl/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/repl/is_master_response.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/repl/is_master_response.h | 4 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_coordinator_mock.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_info.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/repl/topology_coordinator.cpp | 1 |
7 files changed, 160 insertions, 1 deletions
diff --git a/jstests/replsets/awaitable_ismaster.js b/jstests/replsets/awaitable_ismaster.js new file mode 100644 index 00000000000..6fee494311a --- /dev/null +++ b/jstests/replsets/awaitable_ismaster.js @@ -0,0 +1,116 @@ +/** + * Tests the maxAwaitTimeMS and topologyVersion parameters of the isMaster command. + * @tags: [requires_fcv_44] + */ +(function() { +"use strict"; + +// Test isMaster paramaters on a single node replica set. +const replSetName = "awaitable_ismaster_test"; +const replTest = new ReplSetTest({name: replSetName, nodes: 1}); +replTest.startSet(); +replTest.initiate(); + +const dbName = "awaitable_ismaster_test"; +const node = replTest.getPrimary(); +const db = node.getDB(dbName); + +// Check isMaster response contains a topologyVersion even if maxAwaitTimeMS and topologyVersion are +// not included in the request. +let res = assert.commandWorked(db.runCommand({isMaster: 1})); +assert(res.hasOwnProperty("topologyVersion"), tojson(res)); + +const topologyVersionField = res.topologyVersion; +assert(topologyVersionField.hasOwnProperty("processId"), tojson(topologyVersionField)); +assert(topologyVersionField.hasOwnProperty("counter"), tojson(topologyVersionField)); + +// Check that isMaster succeeds when passed a valid toplogyVersion and maxAwaitTimeMS. In this case, +// use the topologyVersion from the previous isMaster response. The topologyVersion field is +// expected to be of the form {processId: <ObjectId>, counter: <long>}. +assert.commandWorked( + db.runCommand({isMaster: 1, topologyVersion: topologyVersionField, maxAwaitTimeMS: 100})); + +// Check that passing a topologyVersion not of type object fails. +assert.commandFailedWithCode( + db.runCommand({isMaster: 1, topologyVersion: "topology_version_string", maxAwaitTimeMS: 100}), + 10065); + +// Check that a topologyVersion with an invalid processId and valid counter fails. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: {processId: "pid1", counter: topologyVersionField.counter}, + maxAwaitTimeMS: 100 +}), + ErrorCodes.TypeMismatch); + +// Check that a topologyVersion with a valid processId and invalid counter fails. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: {processId: topologyVersionField.processId, counter: 0}, + maxAwaitTimeMS: 100 +}), + ErrorCodes.TypeMismatch); + +// Check that a topologyVersion with a valid processId but missing counter fails. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: {processId: topologyVersionField.processId}, + maxAwaitTimeMS: 100 +}), + 40414); + +// Check that a topologyVersion with a missing processId and valid counter fails. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: {counter: topologyVersionField.counter}, + maxAwaitTimeMS: 100 +}), + 40414); + +// Check that a topologyVersion with a valid processId and negative counter fails. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: {processId: topologyVersionField.processId, counter: NumberLong("-1")}, + maxAwaitTimeMS: 100 +}), + 31372); + +// Check that isMaster fails if there is an extra field in its topologyVersion. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: { + processId: topologyVersionField.processId, + counter: topologyVersionField.counter, + randomField: "I should cause an error" + }, + maxAwaitTimeMS: 100 +}), + 40415); + +// A client following the awaitable isMaster protocol must include topologyVersion in their request +// if and only if they include maxAwaitTimeMS. +// Check that isMaster fails if there is a topologyVersion but no maxAwaitTimeMS field. +assert.commandFailedWithCode(db.runCommand({isMaster: 1, topologyVersion: topologyVersionField}), + 31368); + +// Check that isMaster fails if there is a maxAwaitTimeMS field but no topologyVersion. +assert.commandFailedWithCode(db.runCommand({isMaster: 1, maxAwaitTimeMS: 100}), 31368); + +// Check that isMaster fails if there is a valid topologyVersion but invalid maxAwaitTimeMS type. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: topologyVersionField, + maxAwaitTimeMS: "stringMaxAwaitTimeMS" +}), + ErrorCodes.TypeMismatch); + +// Check that isMaster fails if there is a valid topologyVersion but negative maxAwaitTimeMS. +assert.commandFailedWithCode(db.runCommand({ + isMaster: 1, + topologyVersion: topologyVersionField, + maxAwaitTimeMS: -1, +}), + 31373); + +replTest.stopSet(); +})(); diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index ca6c5536583..a77d0d59a03 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -630,7 +630,6 @@ env.Library('topology_coordinator', 'member_data.cpp', 'topology_coordinator.cpp', env.Idlc('topology_coordinator.idl')[0], - env.Idlc('topology_version.idl')[0], ], LIBDEPS=[ '$BUILD_DIR/mongo/db/audit', @@ -741,6 +740,7 @@ env.Library( 'storage_interface_mock.cpp', ], LIBDEPS=[ + 'isself', 'oplog_buffer_blocking_queue', 'repl_coordinator_interface', 'repl_settings', @@ -784,6 +784,7 @@ env.Library('replica_set_messages', 'update_position_args.cpp', 'last_vote.cpp', env.Idlc('repl_set_config.idl')[0], + env.Idlc('topology_version.idl')[0], ], LIBDEPS=[ '$BUILD_DIR/mongo/base', diff --git a/src/mongo/db/repl/is_master_response.cpp b/src/mongo/db/repl/is_master_response.cpp index 06e0d1c1896..6c1efaa73a2 100644 --- a/src/mongo/db/repl/is_master_response.cpp +++ b/src/mongo/db/repl/is_master_response.cpp @@ -47,6 +47,7 @@ const std::string kIsMasterFieldName = "ismaster"; const std::string kSecondaryFieldName = "secondary"; const std::string kSetNameFieldName = "setName"; const std::string kSetVersionFieldName = "setVersion"; +const std::string kTopologyVersionFieldName = "topologyVersion"; const std::string kHostsFieldName = "hosts"; const std::string kPassivesFieldName = "passives"; const std::string kArbitersFieldName = "arbiters"; @@ -102,6 +103,11 @@ IsMasterResponse::IsMasterResponse() _shutdownInProgress(false) {} void IsMasterResponse::addToBSON(BSONObjBuilder* builder) const { + if (_topologyVersion) { + BSONObjBuilder topologyVersionBuilder(builder->subobjStart(kTopologyVersionFieldName)); + _topologyVersion->serialize(&topologyVersionBuilder); + } + if (_hostsSet) { std::vector<std::string> hosts; for (size_t i = 0; i < _hosts.size(); ++i) { @@ -538,6 +544,10 @@ void IsMasterResponse::setShouldBuildIndexes(bool buildIndexes) { _buildIndexes = buildIndexes; } +void IsMasterResponse::setTopologyVersion(TopologyVersion topologyVersion) { + _topologyVersion = topologyVersion; +} + void IsMasterResponse::setSlaveDelay(Seconds slaveDelay) { _slaveDelaySet = true; _slaveDelay = slaveDelay; diff --git a/src/mongo/db/repl/is_master_response.h b/src/mongo/db/repl/is_master_response.h index d8bfd03dfd5..9002ebff632 100644 --- a/src/mongo/db/repl/is_master_response.h +++ b/src/mongo/db/repl/is_master_response.h @@ -35,6 +35,7 @@ #include "mongo/bson/oid.h" #include "mongo/db/repl/optime.h" #include "mongo/db/repl/optime_with.h" +#include "mongo/db/repl/topology_version_gen.h" #include "mongo/stdx/unordered_map.h" #include "mongo/util/net/hostandport.h" #include "mongo/util/time_support.h" @@ -217,6 +218,8 @@ public: void setShouldBuildIndexes(bool buildIndexes); + void setTopologyVersion(TopologyVersion topologyVersion); + void setSlaveDelay(Seconds slaveDelay); void addTag(const std::string& tagKey, const std::string& tagValue); @@ -278,6 +281,7 @@ private: OID _electionId; boost::optional<OpTimeWith<time_t>> _lastWrite; boost::optional<OpTimeWith<time_t>> _lastMajorityWrite; + boost::optional<TopologyVersion> _topologyVersion; // If _configSet is false this means we don't have a valid repl set config, so toBSON // will return a set of hardcoded values that indicate this. diff --git a/src/mongo/db/repl/replication_coordinator_mock.cpp b/src/mongo/db/repl/replication_coordinator_mock.cpp index 1e0f01ec424..453cc76b420 100644 --- a/src/mongo/db/repl/replication_coordinator_mock.cpp +++ b/src/mongo/db/repl/replication_coordinator_mock.cpp @@ -35,6 +35,7 @@ #include "mongo/bson/timestamp.h" #include "mongo/db/namespace_string.h" #include "mongo/db/repl/is_master_response.h" +#include "mongo/db/repl/isself.h" #include "mongo/db/repl/read_concern_args.h" #include "mongo/db/repl/sync_source_resolver.h" #include "mongo/db/write_concern_options.h" @@ -350,6 +351,7 @@ void ReplicationCoordinatorMock::fillIsMasterForReplSet(IsMasterResponse* result result->setIsSecondary(false); result->setMe(_getConfigReturnValue.getMemberAt(0).getHostAndPort()); result->setElectionId(OID::gen()); + result->setTopologyVersion(TopologyVersion(repl::instanceId, 0)); } void ReplicationCoordinatorMock::appendSlaveInfoData(BSONObjBuilder* result) {} diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp index c21394e0145..ad6d066fff4 100644 --- a/src/mongo/db/repl/replication_info.cpp +++ b/src/mongo/db/repl/replication_info.cpp @@ -33,6 +33,7 @@ #include <list> #include <vector> +#include "mongo/bson/util/bson_extract.h" #include "mongo/client/connpool.h" #include "mongo/client/dbclient_connection.h" #include "mongo/db/auth/sasl_mechanism_registry.h" @@ -291,6 +292,30 @@ public: SplitHorizon::setParameters(opCtx->getClient(), std::move(sniName)); } + // If a client is following the awaitable isMaster protocol, maxAwaitTimeMS should be + // present if and only if topologyVersion is present in the request. + auto topologyVersionElement = cmdObj["topologyVersion"]; + auto maxAwaitTimeMSField = cmdObj["maxAwaitTimeMS"]; + if (topologyVersionElement && maxAwaitTimeMSField) { + auto topologyVersion = TopologyVersion::parse(IDLParserErrorContext("TopologyVersion"), + topologyVersionElement.Obj()); + uassert(31372, + "topologyVersion must have a non-negative counter", + topologyVersion.getCounter() >= 0); + + long long maxAwaitTimeMSValue; + uassertStatusOK( + bsonExtractIntegerField(cmdObj, "maxAwaitTimeMS", &maxAwaitTimeMSValue)); + uassert( + 31373, "maxAwaitTimeMS must be a non-negative integer", maxAwaitTimeMSValue >= 0); + } else { + uassert(31368, + (topologyVersionElement + ? "A request with a 'topologyVersion' must include 'maxAwaitTimeMS'" + : "A request with 'maxAwaitTimeMS' must include a 'topologyVersion'"), + !topologyVersionElement && !maxAwaitTimeMSField); + } + // Parse the optional 'internalClient' field. This is provided by incoming connections from // mongod and mongos. auto internalClientElement = cmdObj["internalClient"]; diff --git a/src/mongo/db/repl/topology_coordinator.cpp b/src/mongo/db/repl/topology_coordinator.cpp index 372619c6a62..b95d813a3f0 100644 --- a/src/mongo/db/repl/topology_coordinator.cpp +++ b/src/mongo/db/repl/topology_coordinator.cpp @@ -1723,6 +1723,7 @@ void TopologyCoordinator::fillMemberData(BSONObjBuilder* result) { void TopologyCoordinator::fillIsMasterForReplSet(IsMasterResponse* const response, const SplitHorizon::Parameters& horizonParams) { + response->setTopologyVersion(getTopologyVersion()); const MemberState myState = getMemberState(); if (!_rsConfig.isInitialized()) { response->markAsNoConfig(); |