summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2017-10-30 15:46:36 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2017-11-09 09:39:44 -0500
commit271879b7a67c9d9b36778692f2a77e04c6403a1f (patch)
treea4e8911bcb4ac5a0a494725db9b65e92b0f44ae6
parent9a7ab2468ec94462890395cc591cd629d1dd9f7c (diff)
downloadmongo-271879b7a67c9d9b36778692f2a77e04c6403a1f.tar.gz
SERVER-31631 Bump minimum outgoing wire version for mongod when featureCompatibilityVersion is 3.6
-rw-r--r--jstests/multiVersion/feature_compatibility_version_lagging_secondary.js68
-rw-r--r--jstests/multiVersion/migration_between_mixed_FCV_mixed_version_mongods.js47
-rw-r--r--src/mongo/db/commands/feature_compatibility_version.cpp36
-rw-r--r--src/mongo/db/commands/feature_compatibility_version.h6
-rw-r--r--src/mongo/db/db.cpp10
-rw-r--r--src/mongo/db/dbdirectclient.cpp4
-rw-r--r--src/mongo/db/repl/check_quorum_for_config_change.cpp9
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_args.cpp29
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_args.h14
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_args_v1.cpp26
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_args_v1.h18
-rw-r--r--src/mongo/db/repl/replication_info.cpp21
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl.cpp8
-rw-r--r--src/mongo/db/wire_version.h34
-rw-r--r--src/mongo/dbtests/dbtests.cpp16
-rw-r--r--src/mongo/executor/network_interface_asio_test.cpp24
-rw-r--r--src/mongo/s/commands/cluster_is_master_cmd.cpp4
17 files changed, 335 insertions, 39 deletions
diff --git a/jstests/multiVersion/feature_compatibility_version_lagging_secondary.js b/jstests/multiVersion/feature_compatibility_version_lagging_secondary.js
new file mode 100644
index 00000000000..b56e219cac3
--- /dev/null
+++ b/jstests/multiVersion/feature_compatibility_version_lagging_secondary.js
@@ -0,0 +1,68 @@
+// Tests that a primary with upgrade featureCompatibilityVersion cannot connect with a secondary
+// with a lower binary version.
+(function() {
+ "use strict";
+
+ load("jstests/libs/write_concern_util.js");
+
+ const latest = "latest";
+ const downgrade = "3.4";
+
+ const upgradeFCV = "3.6";
+ const downgradeFCV = "3.4";
+
+ function testProtocolVersion(protocolVersion) {
+ jsTestLog("Testing connections between mixed-version mixed-featureCompatibilityVersion " +
+ "nodes in a replica set using protocol version " + protocolVersion);
+
+ // Start a new replica set with two latest version nodes.
+ let rst = new ReplSetTest({
+ protocolVersion: protocolVersion,
+ nodes: [{binVersion: latest}, {binVersion: latest, rsConfig: {priority: 0}}],
+ settings: {chainingAllowed: false}
+ });
+ rst.startSet();
+
+ // The default value for 'catchUpTimeoutMillis' on 3.6 is -1. A 3.4 secondary will refuse to
+ // join a replica set with catchUpTimeoutMillis=-1.
+ let replSetConfig = rst.getReplSetConfig();
+ replSetConfig.settings.catchUpTimeoutMillis = 2000;
+ rst.initiate(replSetConfig);
+
+ let primary = rst.getPrimary();
+ let latestSecondary = rst.getSecondary();
+
+ // Set the featureCompatibilityVersion to the downgrade version so that a downgrade node can
+ // join the set.
+ assert.commandWorked(
+ primary.getDB("admin").runCommand({setFeatureCompatibilityVersion: downgradeFCV}));
+
+ // Add a downgrade node to the set.
+ let downgradeSecondary = rst.add({binVersion: downgrade, rsConfig: {priority: 0}});
+ rst.reInitiate();
+
+ // Wait for the downgrade secondary to finish initial sync.
+ rst.awaitSecondaryNodes();
+ rst.awaitReplication();
+
+ // Stop replication on the downgrade secondary.
+ stopServerReplication(downgradeSecondary);
+
+ // Set the featureCompatibilityVersion to the upgrade version. This will not replicate to
+ // the downgrade secondary, but the downgrade secondary will no longer be able to
+ // communicate with the rest of the set.
+ assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: upgradeFCV}));
+
+ // Shut down the latest version secondary.
+ rst.stop(latestSecondary);
+
+ // The primary should step down, since it can no longer see a majority of the replica set.
+ rst.waitForState(primary, ReplSetTest.State.SECONDARY);
+
+ restartServerReplication(downgradeSecondary);
+ rst.stopSet();
+ }
+
+ testProtocolVersion(0);
+ testProtocolVersion(1);
+})();
diff --git a/jstests/multiVersion/migration_between_mixed_FCV_mixed_version_mongods.js b/jstests/multiVersion/migration_between_mixed_FCV_mixed_version_mongods.js
new file mode 100644
index 00000000000..7c79af655b0
--- /dev/null
+++ b/jstests/multiVersion/migration_between_mixed_FCV_mixed_version_mongods.js
@@ -0,0 +1,47 @@
+/**
+ * Test that it is not possible to move a chunk from an upgrade featureCompatibilityVersion node to
+ * a downgrade binary version node.
+ */
+
+// This test will not end with consistent UUIDs, since there is inconsistent
+// featureCompatibilityVersion across the cluster.
+TestData.skipCheckingUUIDsConsistentAcrossCluster = true;
+
+(function() {
+ "use strict";
+
+ load("jstests/libs/feature_compatibility_version.js");
+
+ const upgradeVersion = "3.6";
+ const downgradeVersion = "3.4";
+
+ let st = new ShardingTest({
+ shards: [{binVersion: "latest"}, {binVersion: downgradeVersion}],
+ mongos: {binVersion: "latest"}
+ });
+
+ let testDB = st.s.getDB("test");
+
+ // Create a sharded collection with primary shard 0.
+ assert.commandWorked(st.s.adminCommand({enableSharding: testDB.getName()}));
+ st.ensurePrimaryShard(testDB.getName(), st.shard0.shardName);
+ assert.commandWorked(
+ st.s.adminCommand({shardCollection: testDB.coll.getFullName(), key: {a: 1}}));
+
+ // Set the featureCompatibilityVersion to 3.6. This will fail because the
+ // featureCompatibilityVersion cannot be set to 3.6 on shard 1, but it will set the
+ // featureCompatibilityVersion to 3.6 on shard 0.
+ assert.commandFailed(st.s.adminCommand({setFeatureCompatibilityVersion: upgradeVersion}));
+ checkFCV(st.configRS.getPrimary().getDB("admin"), downgradeVersion, upgradeVersion);
+ checkFCV(st.shard0.getDB("admin"), upgradeVersion);
+ checkFCV34(st.shard1.getDB("admin"), downgradeVersion);
+
+ // It is not possible to move a chunk from an upgrade featureCompatibilityVersion shard to a
+ // downgrade shard.
+ assert.commandFailedWithCode(
+ st.s.adminCommand(
+ {moveChunk: testDB.coll.getFullName(), find: {a: 1}, to: st.shard1.shardName}),
+ ErrorCodes.IncompatibleServerVersion);
+
+ st.stop();
+})();
diff --git a/src/mongo/db/commands/feature_compatibility_version.cpp b/src/mongo/db/commands/feature_compatibility_version.cpp
index 8a2ed8a54ad..9e31469fe51 100644
--- a/src/mongo/db/commands/feature_compatibility_version.cpp
+++ b/src/mongo/db/commands/feature_compatibility_version.cpp
@@ -40,6 +40,7 @@
#include "mongo/db/server_parameters.h"
#include "mongo/db/service_context.h"
#include "mongo/db/storage/storage_engine.h"
+#include "mongo/db/wire_version.h"
#include "mongo/db/write_concern_options.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/transport/service_entry_point.h"
@@ -340,8 +341,11 @@ void FeatureCompatibilityVersion::onInsertOrUpdate(OperationContext* opCtx, cons
// version that is below the minimum.
opCtx->recoveryUnit()->onCommit([opCtx, newVersion]() {
serverGlobalParams.featureCompatibility.setVersion(newVersion);
+ updateMinWireVersion();
- // Close all connections from internal clients with binary versions lower than 3.6.
+ // Close all incoming connections from internal clients with binary versions lower than
+ // ours. It would be desirable to close all outgoing connections to servers with lower
+ // binary version, but it is not currently possible.
if (newVersion != ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34) {
opCtx->getServiceContext()->getServiceEntryPoint()->endAllSessions(
transport::Session::kLatestVersionInternalClientKeepOpen |
@@ -367,6 +371,7 @@ void FeatureCompatibilityVersion::onDelete(OperationContext* opCtx, const BSONOb
opCtx->recoveryUnit()->onCommit([]() {
serverGlobalParams.featureCompatibility.setVersion(
ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34);
+ updateMinWireVersion();
});
}
@@ -384,9 +389,35 @@ void FeatureCompatibilityVersion::onDropCollection(OperationContext* opCtx) {
opCtx->recoveryUnit()->onCommit([]() {
serverGlobalParams.featureCompatibility.setVersion(
ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34);
+ updateMinWireVersion();
});
}
+void FeatureCompatibilityVersion::updateMinWireVersion() {
+ WireSpec& spec = WireSpec::instance();
+
+ switch (serverGlobalParams.featureCompatibility.getVersion()) {
+ case ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo36:
+ case ServerGlobalParams::FeatureCompatibility::Version::kUpgradingTo36:
+ case ServerGlobalParams::FeatureCompatibility::Version::kDowngradingTo34:
+ spec.incomingInternalClient.minWireVersion = LATEST_WIRE_VERSION;
+ spec.outgoing.minWireVersion = LATEST_WIRE_VERSION;
+ return;
+ case ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34:
+ // It would be preferable to set 'incomingInternalClient.minWireVersion' and
+ // 'outgoing.minWireVersion' to LATEST_WIRE_VERSION - 1, but this is not possible due to
+ // a bug in 3.4, where if the receiving node says it supports wire version range
+ // [COMMANDS_ACCEPT_WRITE_CONCERN, SUPPORTS_OP_MSG], the initiating node will think it
+ // only supports OP_QUERY.
+ spec.incomingInternalClient.minWireVersion = RELEASE_2_4_AND_BEFORE;
+ spec.outgoing.minWireVersion = RELEASE_2_4_AND_BEFORE;
+ return;
+ case ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault34Behavior:
+ // getVersion() does not return this value.
+ MONGO_UNREACHABLE;
+ }
+}
+
void FeatureCompatibilityVersion::_validateVersion(StringData version) {
uassert(40284,
str::stream() << "featureCompatibilityVersion must be '"
@@ -471,7 +502,8 @@ public:
FeatureCompatibilityVersion::kTargetVersionField,
FeatureCompatibilityVersionCommandParser::kVersion34);
return;
- default:
+ case ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault34Behavior:
+ // getVersion() does not return this value.
MONGO_UNREACHABLE;
}
}
diff --git a/src/mongo/db/commands/feature_compatibility_version.h b/src/mongo/db/commands/feature_compatibility_version.h
index e1598756919..70acd034ef3 100644
--- a/src/mongo/db/commands/feature_compatibility_version.h
+++ b/src/mongo/db/commands/feature_compatibility_version.h
@@ -134,6 +134,12 @@ public:
*/
static void onDropCollection(OperationContext* opCtx);
+ /**
+ * Sets the server's outgoing and incomingInternalClient minWireVersions according to the
+ * current featureCompatibilityVersion value.
+ */
+ static void updateMinWireVersion();
+
private:
/**
* Validate version. Uasserts if invalid.
diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp
index b18a5b40010..2d191c38220 100644
--- a/src/mongo/db/db.cpp
+++ b/src/mongo/db/db.cpp
@@ -546,6 +546,7 @@ StatusWith<bool> repairDatabasesAndCheckVersion(OperationContext* opCtx) {
fcvDocumentExists = true;
auto version = swVersion.getValue();
serverGlobalParams.featureCompatibility.setVersion(version);
+ FeatureCompatibilityVersion::updateMinWireVersion();
// On startup, if the version is in an upgrading or downrading state, print a
// warning.
@@ -662,6 +663,15 @@ StatusWith<bool> repairDatabasesAndCheckVersion(OperationContext* opCtx) {
void initWireSpec() {
WireSpec& spec = WireSpec::instance();
+ // The featureCompatibilityVersion behavior defaults to the downgrade behavior while the
+ // in-memory version is unset.
+
+ spec.incomingInternalClient.minWireVersion = RELEASE_2_4_AND_BEFORE;
+ spec.incomingInternalClient.maxWireVersion = LATEST_WIRE_VERSION;
+
+ spec.outgoing.minWireVersion = RELEASE_2_4_AND_BEFORE;
+ spec.outgoing.maxWireVersion = LATEST_WIRE_VERSION;
+
spec.isInternalClient = true;
}
diff --git a/src/mongo/db/dbdirectclient.cpp b/src/mongo/db/dbdirectclient.cpp
index c644f2dd631..132a01131ce 100644
--- a/src/mongo/db/dbdirectclient.cpp
+++ b/src/mongo/db/dbdirectclient.cpp
@@ -95,12 +95,12 @@ std::string DBDirectClient::getServerAddress() const {
// Returned version should match the incoming connections restrictions.
int DBDirectClient::getMinWireVersion() {
- return WireSpec::instance().incoming.minWireVersion;
+ return WireSpec::instance().incomingExternalClient.minWireVersion;
}
// Returned version should match the incoming connections restrictions.
int DBDirectClient::getMaxWireVersion() {
- return WireSpec::instance().incoming.maxWireVersion;
+ return WireSpec::instance().incomingExternalClient.maxWireVersion;
}
bool DBDirectClient::isReplicaSetMember() const {
diff --git a/src/mongo/db/repl/check_quorum_for_config_change.cpp b/src/mongo/db/repl/check_quorum_for_config_change.cpp
index 83489872c63..e1a2321d91e 100644
--- a/src/mongo/db/repl/check_quorum_for_config_change.cpp
+++ b/src/mongo/db/repl/check_quorum_for_config_change.cpp
@@ -40,6 +40,7 @@
#include "mongo/db/repl/repl_set_heartbeat_response.h"
#include "mongo/db/repl/scatter_gather_algorithm.h"
#include "mongo/db/repl/scatter_gather_runner.h"
+#include "mongo/db/server_options.h"
#include "mongo/rpc/metadata/repl_set_metadata.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
@@ -89,6 +90,10 @@ std::vector<RemoteCommandRequest> QuorumChecker::getRequests() const {
hbArgs.setSetName(_rsConfig->getReplSetName());
hbArgs.setProtocolVersion(1);
hbArgs.setConfigVersion(_rsConfig->getConfigVersion());
+ if (serverGlobalParams.featureCompatibility.getVersion() !=
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34) {
+ hbArgs.setHeartbeatVersion(1);
+ }
hbArgs.setCheckEmpty(isInitialConfig);
hbArgs.setSenderHost(myConfig.getHostAndPort());
hbArgs.setSenderId(myConfig.getId());
@@ -98,6 +103,10 @@ std::vector<RemoteCommandRequest> QuorumChecker::getRequests() const {
ReplSetHeartbeatArgsV1 hbArgs;
hbArgs.setSetName(_rsConfig->getReplSetName());
hbArgs.setConfigVersion(_rsConfig->getConfigVersion());
+ if (serverGlobalParams.featureCompatibility.getVersion() !=
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34) {
+ hbArgs.setHeartbeatVersion(1);
+ }
if (isInitialConfig) {
hbArgs.setCheckEmpty();
}
diff --git a/src/mongo/db/repl/repl_set_heartbeat_args.cpp b/src/mongo/db/repl/repl_set_heartbeat_args.cpp
index db88f7e47b6..5b75f97c516 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_args.cpp
+++ b/src/mongo/db/repl/repl_set_heartbeat_args.cpp
@@ -42,6 +42,7 @@ namespace {
const std::string kCheckEmptyFieldName = "checkEmpty";
const std::string kProtocolVersionFieldName = "pv";
const std::string kConfigVersionFieldName = "v";
+const std::string kHeartbeatVersionFieldName = "hbv";
const std::string kSenderIdFieldName = "fromId";
const std::string kSetNameFieldName = "replSetHeartbeat";
const std::string kSenderHostFieldName = "from";
@@ -49,6 +50,7 @@ const std::string kSenderHostFieldName = "from";
const std::string kLegalHeartbeatFieldNames[] = {kCheckEmptyFieldName,
kProtocolVersionFieldName,
kConfigVersionFieldName,
+ kHeartbeatVersionFieldName,
kSenderIdFieldName,
kSetNameFieldName,
kSenderHostFieldName};
@@ -59,12 +61,14 @@ ReplSetHeartbeatArgs::ReplSetHeartbeatArgs()
: _hasCheckEmpty(false),
_hasProtocolVersion(false),
_hasConfigVersion(false),
+ _hasHeartbeatVersion(false),
_hasSenderId(false),
_hasSetName(false),
_hasSenderHost(false),
_checkEmpty(false),
_protocolVersion(-1),
_configVersion(-1),
+ _heartbeatVersion(-1),
_senderId(-1),
_setName(""),
_senderHost(HostAndPort()) {}
@@ -90,6 +94,22 @@ Status ReplSetHeartbeatArgs::initialize(const BSONObj& argsObj) {
return status;
_hasConfigVersion = true;
+ long long tempHeartbeatVersion;
+ status = bsonExtractIntegerField(argsObj, kHeartbeatVersionFieldName, &tempHeartbeatVersion);
+ if (status.isOK()) {
+ if (tempHeartbeatVersion != 1) {
+ return Status(ErrorCodes::Error(40665),
+ str::stream() << "Found invalid value for field "
+ << kHeartbeatVersionFieldName
+ << ": "
+ << tempHeartbeatVersion);
+ }
+ _heartbeatVersion = tempHeartbeatVersion;
+ _hasHeartbeatVersion = true;
+ } else if (status.code() != ErrorCodes::NoSuchKey) {
+ return status;
+ }
+
status = bsonExtractIntegerFieldWithDefault(argsObj, kSenderIdFieldName, -1, &_senderId);
if (!status.isOK())
return status;
@@ -126,6 +146,10 @@ BSONObj ReplSetHeartbeatArgs::toBSON() const {
builder.append("replSetHeartbeat", _setName);
builder.appendIntOrLL("pv", _protocolVersion);
builder.appendIntOrLL("v", _configVersion);
+ if (_hasHeartbeatVersion) {
+ builder.append("hbv", _heartbeatVersion);
+ }
+
builder.append("from", _hasSenderHost ? _senderHost.toString() : "");
if (_hasSenderId) {
@@ -152,6 +176,11 @@ void ReplSetHeartbeatArgs::setConfigVersion(long long newVal) {
_hasConfigVersion = true;
}
+void ReplSetHeartbeatArgs::setHeartbeatVersion(long long newVal) {
+ _heartbeatVersion = newVal;
+ _hasHeartbeatVersion = true;
+}
+
void ReplSetHeartbeatArgs::setSenderId(long long newVal) {
_senderId = newVal;
_hasSenderId = true;
diff --git a/src/mongo/db/repl/repl_set_heartbeat_args.h b/src/mongo/db/repl/repl_set_heartbeat_args.h
index 1faa7d4bca0..40e8e180312 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_args.h
+++ b/src/mongo/db/repl/repl_set_heartbeat_args.h
@@ -78,6 +78,14 @@ public:
}
/**
+ * Gets the heartbeat version number of the sender. This field was added to ensure that
+ * heartbeats sent from featureCompatibilityVersion 3.6 nodes to binary version 3.4 nodes fail.
+ */
+ long long getHeartbeatVersion() const {
+ return _heartbeatVersion;
+ }
+
+ /**
* Gets the _id of the sender in their ReplSetConfig.
*/
long long getSenderId() const {
@@ -110,6 +118,9 @@ public:
bool hasConfigVersion() const {
return _hasConfigVersion;
}
+ bool hasHeartbeatVersion() const {
+ return _hasHeartbeatVersion;
+ }
bool hasSenderId() const {
return _hasSenderId;
}
@@ -126,6 +137,7 @@ public:
void setCheckEmpty(bool newVal);
void setProtocolVersion(long long newVal);
void setConfigVersion(long long newVal);
+ void setHeartbeatVersion(long long newVal);
void setSenderId(long long newVal);
void setSetName(std::string newVal);
void setSenderHost(HostAndPort newVal);
@@ -141,6 +153,7 @@ private:
bool _hasCheckEmpty;
bool _hasProtocolVersion;
bool _hasConfigVersion;
+ bool _hasHeartbeatVersion;
bool _hasSenderId;
bool _hasSetName;
bool _hasSenderHost;
@@ -149,6 +162,7 @@ private:
bool _checkEmpty;
long long _protocolVersion;
long long _configVersion;
+ long long _heartbeatVersion;
long long _senderId;
std::string _setName;
HostAndPort _senderHost;
diff --git a/src/mongo/db/repl/repl_set_heartbeat_args_v1.cpp b/src/mongo/db/repl/repl_set_heartbeat_args_v1.cpp
index 39bab8cdda7..4568e3ef89c 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_args_v1.cpp
+++ b/src/mongo/db/repl/repl_set_heartbeat_args_v1.cpp
@@ -42,6 +42,7 @@ namespace {
const std::string kCheckEmptyFieldName = "checkEmpty";
const std::string kConfigVersionFieldName = "configVersion";
+const std::string kHeartbeatVersionFieldName = "hbv";
const std::string kSenderHostFieldName = "from";
const std::string kSenderIdFieldName = "fromId";
const std::string kSetNameFieldName = "replSetHeartbeat";
@@ -49,6 +50,7 @@ const std::string kTermFieldName = "term";
const std::string kLegalHeartbeatFieldNames[] = {kCheckEmptyFieldName,
kConfigVersionFieldName,
+ kHeartbeatVersionFieldName,
kSenderHostFieldName,
kSenderIdFieldName,
kSetNameFieldName,
@@ -70,6 +72,22 @@ Status ReplSetHeartbeatArgsV1::initialize(const BSONObj& argsObj) {
if (!status.isOK())
return status;
+ long long tempHeartbeatVersion;
+ status = bsonExtractIntegerField(argsObj, kHeartbeatVersionFieldName, &tempHeartbeatVersion);
+ if (status.isOK()) {
+ if (tempHeartbeatVersion != 1) {
+ return Status(ErrorCodes::Error(40666),
+ str::stream() << "Found invalid value for field "
+ << kHeartbeatVersionFieldName
+ << ": "
+ << tempHeartbeatVersion);
+ }
+ _heartbeatVersion = tempHeartbeatVersion;
+ _hasHeartbeatVersion = true;
+ } else if (status.code() != ErrorCodes::NoSuchKey) {
+ return status;
+ }
+
status = bsonExtractIntegerFieldWithDefault(argsObj, kSenderIdFieldName, -1, &_senderId);
if (!status.isOK())
return status;
@@ -104,6 +122,11 @@ void ReplSetHeartbeatArgsV1::setConfigVersion(long long newVal) {
_configVersion = newVal;
}
+void ReplSetHeartbeatArgsV1::setHeartbeatVersion(long long newVal) {
+ _heartbeatVersion = newVal;
+ _hasHeartbeatVersion = true;
+}
+
void ReplSetHeartbeatArgsV1::setSenderHost(const HostAndPort& newVal) {
_senderHost = newVal;
_hasSender = true;
@@ -138,6 +161,9 @@ void ReplSetHeartbeatArgsV1::addToBSON(BSONObjBuilder* builder) const {
builder->append(kCheckEmptyFieldName, _checkEmpty);
}
builder->appendIntOrLL(kConfigVersionFieldName, _configVersion);
+ if (_hasHeartbeatVersion) {
+ builder->appendIntOrLL(kHeartbeatVersionFieldName, _hasHeartbeatVersion);
+ }
builder->append(kSenderHostFieldName, _hasSender ? _senderHost.toString() : "");
builder->appendIntOrLL(kSenderIdFieldName, _senderId);
builder->appendIntOrLL(kTermFieldName, _term);
diff --git a/src/mongo/db/repl/repl_set_heartbeat_args_v1.h b/src/mongo/db/repl/repl_set_heartbeat_args_v1.h
index 45a00ab8927..6f1cbb94af8 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_args_v1.h
+++ b/src/mongo/db/repl/repl_set_heartbeat_args_v1.h
@@ -63,6 +63,14 @@ public:
}
/**
+ * Gets the heartbeat version number of the sender. This field was added to ensure that
+ * heartbeats sent from featureCompatibilityVersion 3.6 nodes to binary version 3.4 nodes fail.
+ */
+ long long getHeartbeatVersion() const {
+ return _heartbeatVersion;
+ }
+
+ /**
* Gets the _id of the sender in their ReplSetConfig.
*/
long long getSenderId() const {
@@ -105,9 +113,17 @@ public:
}
/**
+ * Returns whether or not the heartbeat version of the sender is set.
+ */
+ bool hasHeartbeatVersion() const {
+ return _hasHeartbeatVersion;
+ }
+
+ /**
* The below methods set the value in the method name to 'newVal'.
*/
void setConfigVersion(long long newVal);
+ void setHeartbeatVersion(long long newVal);
void setSenderId(long long newVal);
void setSenderHost(const HostAndPort& newVal);
void setSetName(const std::string& newVal);
@@ -126,10 +142,12 @@ public:
private:
// look at the body of the isInitialized() function to see which fields are mandatory
long long _configVersion = -1;
+ long long _heartbeatVersion = -1;
long long _senderId = -1;
long long _term = -1;
bool _checkEmpty = false;
bool _hasSender = false;
+ bool _hasHeartbeatVersion = false;
std::string _setName;
HostAndPort _senderHost;
};
diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp
index 192d7c67b22..7729eaccda3 100644
--- a/src/mongo/db/repl/replication_info.cpp
+++ b/src/mongo/db/repl/replication_info.cpp
@@ -309,7 +309,8 @@ public:
// All incoming connections from mongod/mongos of earlier versions should be
// closed if the featureCompatibilityVersion is bumped to 3.6.
- if (elem.numberInt() >= WireSpec::instance().incoming.maxWireVersion) {
+ if (elem.numberInt() >=
+ WireSpec::instance().incomingInternalClient.maxWireVersion) {
sessionTagsToSet |=
transport::Session::kLatestVersionInternalClientKeepOpen;
} else {
@@ -361,18 +362,18 @@ public:
result.appendNumber("maxMessageSizeBytes", MaxMessageSizeBytes);
result.appendNumber("maxWriteBatchSize", write_ops::kMaxWriteBatchSize);
result.appendDate("localTime", jsTime());
- result.append("maxWireVersion", WireSpec::instance().incoming.maxWireVersion);
result.append("logicalSessionTimeoutMinutes", localLogicalSessionTimeoutMinutes);
- // If the featureCompatibilityVersion is 3.6 (or upgrading or downgrading), respond with
- // minWireVersion=maxWireVersion. Then if the connection is from a mongod/mongos of an
- // earlier version, it will fail to connect.
- if (internalClientElement &&
- serverGlobalParams.featureCompatibility.getVersion() !=
- ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34) {
- result.append("minWireVersion", WireSpec::instance().incoming.maxWireVersion);
+ if (internalClientElement) {
+ result.append("minWireVersion",
+ WireSpec::instance().incomingInternalClient.minWireVersion);
+ result.append("maxWireVersion",
+ WireSpec::instance().incomingInternalClient.maxWireVersion);
} else {
- result.append("minWireVersion", WireSpec::instance().incoming.minWireVersion);
+ result.append("minWireVersion",
+ WireSpec::instance().incomingExternalClient.minWireVersion);
+ result.append("maxWireVersion",
+ WireSpec::instance().incomingExternalClient.maxWireVersion);
}
result.append("readOnly", storageGlobalParams.readOnly);
diff --git a/src/mongo/db/repl/topology_coordinator_impl.cpp b/src/mongo/db/repl/topology_coordinator_impl.cpp
index 00ec2910226..07ec8c6a99d 100644
--- a/src/mongo/db/repl/topology_coordinator_impl.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl.cpp
@@ -902,6 +902,10 @@ std::pair<ReplSetHeartbeatArgs, Milliseconds> TopologyCoordinatorImpl::prepareHe
hbArgs.setSetName(ourSetName);
hbArgs.setConfigVersion(-2);
}
+ if (serverGlobalParams.featureCompatibility.getVersion() !=
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34) {
+ hbArgs.setHeartbeatVersion(1);
+ }
const Milliseconds timeoutPeriod(
_rsConfig.isInitialized() ? _rsConfig.getHeartbeatTimeoutPeriodMillis()
@@ -938,6 +942,10 @@ std::pair<ReplSetHeartbeatArgsV1, Milliseconds> TopologyCoordinatorImpl::prepare
hbArgs.setConfigVersion(-2);
hbArgs.setTerm(OpTime::kInitialTerm);
}
+ if (serverGlobalParams.featureCompatibility.getVersion() !=
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34) {
+ hbArgs.setHeartbeatVersion(1);
+ }
const Milliseconds timeoutPeriod(
_rsConfig.isInitialized() ? _rsConfig.getHeartbeatTimeoutPeriodMillis()
diff --git a/src/mongo/db/wire_version.h b/src/mongo/db/wire_version.h
index 84b353a1d83..cc6050b8834 100644
--- a/src/mongo/db/wire_version.h
+++ b/src/mongo/db/wire_version.h
@@ -107,16 +107,34 @@ struct WireSpec {
static void appendInternalClientWireVersion(WireVersionInfo wireVersionInfo,
BSONObjBuilder* builder);
- // incoming.minWireVersion - Minimum version that the server accepts on incoming requests. We
- // should bump this whenever we don't want to allow incoming connections from clients that are
- // too old.
-
- // incoming.maxWireVersion - Latest version that the server accepts on incoming requests. This
- // should always be at the latest entry in WireVersion.
- WireVersionInfo incoming = {RELEASE_2_4_AND_BEFORE, LATEST_WIRE_VERSION};
+ // incomingExternalClient.minWireVersion - Minimum version that the server accepts on incoming
+ // requests from external clients. We should bump this whenever we don't want to allow incoming
+ // connections from clients that are too old.
+
+ // incomingExternalClient.maxWireVersion - Latest version that the server accepts on incoming
+ // requests from external clients. This should always be at the latest entry in WireVersion.
+ WireVersionInfo incomingExternalClient = {RELEASE_2_4_AND_BEFORE, LATEST_WIRE_VERSION};
+
+ // incomingInternalClient.minWireVersion - Minimum version that the server accepts on incoming
+ // requests from internal clients. This should be incomingInternalClient.maxWireVersion - 1,
+ // when the featureCompatibilityVersion is equal to the downgrade version, and
+ // incomingInternalClient.maxWireVersion otherwise. However, in 3.6, this needs to be
+ // RELEASE_2_4_AND_BEFORE when the featureCompatibilityVersion is equal to the downgrade version
+ // due to a bug in 3.4, where if the receiving node says it supports wire version range
+ // [COMMANDS_ACCEPT_WRITE_CONCERN, SUPPORTS_OP_MSG] and it is a mongod, the initiating node will
+ // think it only supports OP_QUERY.
+
+ // incomingInternalClient.maxWireVersion - Latest version that the server accepts on incoming
+ // requests. This should always be at the latest entry in WireVersion.
+ WireVersionInfo incomingInternalClient = {RELEASE_2_4_AND_BEFORE, LATEST_WIRE_VERSION};
// outgoing.minWireVersion - Minimum version allowed on remote nodes when the server sends
- // requests. We should bump this whenever we don't want to connect to clients that are too old.
+ // requests. This should be outgoing.maxWireVersion - 1, when the featureCompatibilityVersion is
+ // equal to the downgrade version, and outgoing.maxWireVersion otherwise. However, in 3.6, this
+ // needs to be RELEASE_2_4_AND_BEFORE when the featureCompatibilityVersion is equal to the
+ // downgrade version due to a bug in 3.4, where if the receiving node says it supports wire
+ // version range [COMMANDS_ACCEPT_WRITE_CONCERN, SUPPORTS_OP_MSG] and it is a mongod, the
+ // initiating node will think it only supports OP_QUERY.
// outgoing.maxWireVersion - Latest version allowed on remote nodes when the server sends
// requests.
diff --git a/src/mongo/dbtests/dbtests.cpp b/src/mongo/dbtests/dbtests.cpp
index 4fd7416d9fa..0903a83b0c3 100644
--- a/src/mongo/dbtests/dbtests.cpp
+++ b/src/mongo/dbtests/dbtests.cpp
@@ -65,11 +65,17 @@ const auto kIndexVersion = IndexDescriptor::IndexVersion::kV2;
void initWireSpec() {
WireSpec& spec = WireSpec::instance();
- // accept from any version
- spec.incoming.minWireVersion = RELEASE_2_4_AND_BEFORE;
- spec.incoming.maxWireVersion = LATEST_WIRE_VERSION;
- // connect to any version
- spec.outgoing.minWireVersion = RELEASE_2_4_AND_BEFORE;
+
+ // Accept from internal clients of the same version, as in upgrade featureCompatibilityVersion.
+ spec.incomingInternalClient.minWireVersion = LATEST_WIRE_VERSION;
+ spec.incomingInternalClient.maxWireVersion = LATEST_WIRE_VERSION;
+
+ // Accept from any version external client.
+ spec.incomingExternalClient.minWireVersion = RELEASE_2_4_AND_BEFORE;
+ spec.incomingExternalClient.maxWireVersion = LATEST_WIRE_VERSION;
+
+ // Connect to servers of the same version, as in upgrade featureCompatibilityVersion.
+ spec.outgoing.minWireVersion = LATEST_WIRE_VERSION;
spec.outgoing.maxWireVersion = LATEST_WIRE_VERSION;
}
diff --git a/src/mongo/executor/network_interface_asio_test.cpp b/src/mongo/executor/network_interface_asio_test.cpp
index bd751297f4c..ac34727a702 100644
--- a/src/mongo/executor/network_interface_asio_test.cpp
+++ b/src/mongo/executor/network_interface_asio_test.cpp
@@ -60,8 +60,10 @@ HostAndPort testHost{"localhost", 20000};
void initWireSpecMongoD() {
WireSpec& spec = WireSpec::instance();
// accept from any version
- spec.incoming.minWireVersion = RELEASE_2_4_AND_BEFORE;
- spec.incoming.maxWireVersion = COMMANDS_ACCEPT_WRITE_CONCERN;
+ spec.incomingInternalClient.minWireVersion = RELEASE_2_4_AND_BEFORE;
+ spec.incomingInternalClient.maxWireVersion = COMMANDS_ACCEPT_WRITE_CONCERN;
+ spec.incomingExternalClient.minWireVersion = RELEASE_2_4_AND_BEFORE;
+ spec.incomingExternalClient.maxWireVersion = COMMANDS_ACCEPT_WRITE_CONCERN;
// connect to any version
spec.outgoing.minWireVersion = RELEASE_2_4_AND_BEFORE;
spec.outgoing.maxWireVersion = COMMANDS_ACCEPT_WRITE_CONCERN;
@@ -73,9 +75,10 @@ RemoteCommandResponse simulateIsMaster(RemoteCommandRequest request) {
ASSERT_EQ(request.dbname, "admin");
RemoteCommandResponse response;
- response.data = BSON("minWireVersion" << mongo::WireSpec::instance().incoming.minWireVersion
- << "maxWireVersion"
- << mongo::WireSpec::instance().incoming.maxWireVersion);
+ response.data =
+ BSON("minWireVersion" << mongo::WireSpec::instance().incomingExternalClient.minWireVersion
+ << "maxWireVersion"
+ << mongo::WireSpec::instance().incomingExternalClient.maxWireVersion);
return response;
}
@@ -673,11 +676,12 @@ TEST_F(NetworkInterfaceASIOConnectionHookTest, ValidateHostInvalid) {
rpc::Protocol::kOpQuery, [](RemoteCommandRequest request) -> RemoteCommandResponse {
RemoteCommandResponse response;
response.data =
- BSON("minWireVersion" << mongo::WireSpec::instance().incoming.minWireVersion
- << "maxWireVersion"
- << mongo::WireSpec::instance().incoming.maxWireVersion
- << "TESTKEY"
- << "TESTVALUE");
+ BSON("minWireVersion"
+ << mongo::WireSpec::instance().incomingExternalClient.minWireVersion
+ << "maxWireVersion"
+ << mongo::WireSpec::instance().incomingExternalClient.maxWireVersion
+ << "TESTKEY"
+ << "TESTVALUE");
return response;
});
diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp
index dfaab9bc5d4..c8180d2e539 100644
--- a/src/mongo/s/commands/cluster_is_master_cmd.cpp
+++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp
@@ -116,8 +116,8 @@ public:
// Mongos tries to keep exactly the same version range of the server for which
// it is compiled.
- result.append("maxWireVersion", WireSpec::instance().incoming.maxWireVersion);
- result.append("minWireVersion", WireSpec::instance().incoming.minWireVersion);
+ result.append("maxWireVersion", WireSpec::instance().incomingExternalClient.maxWireVersion);
+ result.append("minWireVersion", WireSpec::instance().incomingExternalClient.minWireVersion);
const auto parameter = mapFindWithDefault(ServerParameterSet::getGlobal()->getMap(),
"automationServiceDescriptor",