summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/replsets/awaitable_ismaster.js116
-rw-r--r--src/mongo/db/repl/SConscript3
-rw-r--r--src/mongo/db/repl/is_master_response.cpp10
-rw-r--r--src/mongo/db/repl/is_master_response.h4
-rw-r--r--src/mongo/db/repl/replication_coordinator_mock.cpp2
-rw-r--r--src/mongo/db/repl/replication_info.cpp25
-rw-r--r--src/mongo/db/repl/topology_coordinator.cpp1
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();