diff options
author | Andy Schwerin <schwerin@mongodb.com> | 2014-08-06 13:54:00 -0400 |
---|---|---|
committer | Andy Schwerin <schwerin@mongodb.com> | 2014-08-06 13:54:59 -0400 |
commit | 5aa26641648876ee10dcfc8ff7256dd46efc8b9c (patch) | |
tree | 434c4a367628fe2ce236221d9a37e85ffe8c7d4f /src/mongo/db/repl/check_quorum_for_config_change_test.cpp | |
parent | b6f7a03697f160c965b467e8962f7f29934935c4 (diff) | |
download | mongo-5aa26641648876ee10dcfc8ff7256dd46efc8b9c.tar.gz |
SERVER-14626 Implement quorum check functionality for use in replcia set initiate and reconfig.
Diffstat (limited to 'src/mongo/db/repl/check_quorum_for_config_change_test.cpp')
-rw-r--r-- | src/mongo/db/repl/check_quorum_for_config_change_test.cpp | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/src/mongo/db/repl/check_quorum_for_config_change_test.cpp b/src/mongo/db/repl/check_quorum_for_config_change_test.cpp new file mode 100644 index 00000000000..4a1396fb780 --- /dev/null +++ b/src/mongo/db/repl/check_quorum_for_config_change_test.cpp @@ -0,0 +1,545 @@ +/** + * Copyright (C) 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include <boost/thread.hpp> +#include <boost/scoped_ptr.hpp> + +#include "mongo/base/status.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/repl/check_quorum_for_config_change.h" +#include "mongo/db/repl/network_interface_mock.h" +#include "mongo/db/repl/replica_set_config.h" +#include "mongo/db/repl/replication_executor.h" +#include "mongo/stdx/functional.h" +#include "mongo/unittest/unittest.h" + +#define ASSERT_REASON_CONTAINS(STATUS, PATTERN) do { \ + const mongo::Status s_ = (STATUS); \ + ASSERT_FALSE(s_.reason().find(PATTERN) == std::string::npos) << \ + #STATUS ".reason() == " << s_.reason(); \ + } while (false) + +#define ASSERT_NOT_REASON_CONTAINS(STATUS, PATTERN) do { \ + const mongo::Status s_ = (STATUS); \ + ASSERT_TRUE(s_.reason().find(PATTERN) == std::string::npos) << \ + #STATUS ".reason() == " << s_.reason(); \ + } while (false) + +namespace mongo { +namespace repl { +namespace { + + typedef ReplicationExecutor::RemoteCommandRequest RemoteCommandRequest; + + class CheckQuorumTest : public mongo::unittest::Test { + protected: + NetworkInterfaceMock* _net; + boost::scoped_ptr<ReplicationExecutor> _executor; + boost::scoped_ptr<boost::thread> _executorThread; + + private: + void setUp(); + void tearDown(); + }; + + class CheckQuorumForInitiate : public CheckQuorumTest {}; + class CheckQuorumForReconfig : public CheckQuorumTest {}; + + void CheckQuorumTest::setUp() { + _net = new NetworkInterfaceMock; + _executor.reset(new ReplicationExecutor(_net)); + _executorThread.reset(new boost::thread(stdx::bind(&ReplicationExecutor::run, + _executor.get()))); + } + + void CheckQuorumTest::tearDown() { + _net->unblockAll(); + _executor->shutdown(); + _executorThread->join(); + } + + ReplicaSetConfig assertMakeRSConfig(const BSONObj& configBson) { + ReplicaSetConfig config; + ASSERT_OK(config.initialize(configBson)); + ASSERT_OK(config.validate()); + return config; + } + + TEST_F(CheckQuorumForInitiate, ValidSingleNodeSet) { + ReplicaSetConfig config = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1")))); + ASSERT_OK(checkQuorumForInitiate(_executor.get(), config, 0)); + } + + TEST_F(CheckQuorumForInitiate, QuorumCheckCanceledByShutdown) { + _executor->shutdown(); + ReplicaSetConfig config = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1")))); + ASSERT_EQUALS(ErrorCodes::ShutdownInProgress, + checkQuorumForInitiate(_executor.get(), config, 0)); + } + + TEST_F(CheckQuorumForInitiate, QuorumCheckFailedDueToSeveralDownNodes) { + // In this test, "we" are host "h3:1". All other nodes time out on + // their heartbeat request, and so the quorum check for initiate + // will fail because some members were unavailable. + ReplicaSetConfig config = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1") << + BSON("_id" << 5 << "host" << "h5:1")))); + Status status = checkQuorumForInitiate(_executor.get(), config, 2); + ASSERT_EQUALS(ErrorCodes::NodeNotFound, status); + ASSERT_REASON_CONTAINS( + status, "Could not contact the following nodes during replica set initiation"); + ASSERT_REASON_CONTAINS(status, "h1:1"); + ASSERT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + ASSERT_REASON_CONTAINS(status, "h4:1"); + ASSERT_REASON_CONTAINS(status, "h5:1"); + } + + const BSONObj makeHeartbeatRequest(const ReplicaSetConfig& rsConfig, int myConfigIndex) { + const MemberConfig& myConfig = rsConfig.getMemberAt(myConfigIndex); + return BSON( + "replSetHeartbeat" << rsConfig.getReplSetName() << + "v" << rsConfig.getConfigVersion() << + "pv" << 1 << + "checkEmpty" << (rsConfig.getConfigVersion() == 1) << + "from" << myConfig.getHostAndPort().toString() << + "fromId" << myConfig.getId()); + } + + TEST_F(CheckQuorumForInitiate, QuorumCheckSuccessForFiveNodes) { + // In this test, "we" are host "h3:1". All nodes respond successfully to their heartbeat + // requests, and the quorum check succeeds. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1") << + BSON("_id" << 5 << "host" << "h5:1")))); + const int myConfigIndex = 2; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h2", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h4", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + + ASSERT_OK(checkQuorumForInitiate(_executor.get(), rsConfig, myConfigIndex)); + } + + TEST_F(CheckQuorumForInitiate, QuorumCheckFailedDueToOneDownNode) { + // In this test, "we" are host "h3:1". All nodes except "h2:1" respond + // successfully to their heartbeat requests, but quorum check fails because + // all nodes must be available for initiate. This is so even though "h2" + // is neither voting nor electable. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1" << + "priority" << 0 << "votes" << 0) << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1") << + BSON("_id" << 5 << "host" << "h5:1") << + BSON("_id" << 6 << "host" << "h6:1")))); + const int myConfigIndex = 2; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h4", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h6", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + + Status status = checkQuorumForInitiate(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NodeNotFound, status); + ASSERT_REASON_CONTAINS( + status, "Could not contact the following nodes during replica set initiation"); + ASSERT_NOT_REASON_CONTAINS(status, "h1:1"); + ASSERT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h4:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h5:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h6:1"); + } + + TEST_F(CheckQuorumForInitiate, QuorumCheckFailedDueToSetNameMismatch) { + // In this test, "we" are host "h3:1". All nodes respond + // successfully to their heartbeat requests, but quorum check fails because + // "h4" declares that the requested replica set name was not what it expected. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1") << + BSON("_id" << 5 << "host" << "h5:1")))); + const int myConfigIndex = 2; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h2", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h4", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 0 << "mismatch" << true))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + + Status status = checkQuorumForInitiate(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NewReplicaSetConfigurationIncompatible, status); + ASSERT_REASON_CONTAINS( + status, "Our set name did not match"); + ASSERT_NOT_REASON_CONTAINS(status, "h1:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + ASSERT_REASON_CONTAINS(status, "h4:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h5:1"); + } + + TEST_F(CheckQuorumForInitiate, QuorumCheckFailedDueToInitializedNode) { + // In this test, "we" are host "h3:1". All nodes respond + // successfully to their heartbeat requests, but quorum check fails because + // "h5" declares that it is already initialized. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1") << + BSON("_id" << 5 << "host" << "h5:1")))); + const int myConfigIndex = 2; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h2", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h4", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 0 << "set" << "rs0" << "v" << 1))); + + Status status = checkQuorumForInitiate(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NewReplicaSetConfigurationIncompatible, status); + ASSERT_REASON_CONTAINS( + status, "Our config version of"); + ASSERT_REASON_CONTAINS( + status, "is no larger than the version"); + ASSERT_NOT_REASON_CONTAINS(status, "h1:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h4:1"); + ASSERT_REASON_CONTAINS(status, "h5:1"); + } + + TEST_F(CheckQuorumForInitiate, QuorumCheckFailedDueToInitializedNodeOnlyOneRespondent) { + // In this test, "we" are host "h3:1". Only node "h5" responds before the test completes, + // and quorum check fails because "h5" declares that it is already initialized. + // + // Compare to QuorumCheckFailedDueToInitializedNode, above. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 1 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1") << + BSON("_id" << 5 << "host" << "h5:1")))); + const int myConfigIndex = 2; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + // Responses from nodes h1, h2 and h4 and blocked until after the test + // completes. + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1)), + true); + _net->addResponse(RemoteCommandRequest(HostAndPort("h2", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1)), + true); + _net->addResponse(RemoteCommandRequest(HostAndPort("h4", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1)), + true); + + // h5 responds, with a version incompatibility. + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 0 << "set" << "rs0" << "v" << 1))); + + Status status = checkQuorumForInitiate(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NewReplicaSetConfigurationIncompatible, status); + ASSERT_REASON_CONTAINS( + status, "Our config version of"); + ASSERT_REASON_CONTAINS( + status, "is no larger than the version"); + ASSERT_NOT_REASON_CONTAINS(status, "h1:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h4:1"); + ASSERT_REASON_CONTAINS(status, "h5:1"); + } + + TEST_F(CheckQuorumForReconfig, QuorumCheckVetoedDueToHigherConfigVersion) { + // In this test, "we" are host "h3:1". The request to "h2" times out, + // and the request to "h1" comes back indicating a higher config version. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 2 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1")))); + const int myConfigIndex = 2; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 0 << "set" << "rs0" << "v" << 5))); + + Status status = checkQuorumForReconfig(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NewReplicaSetConfigurationIncompatible, status); + ASSERT_REASON_CONTAINS( + status, "Our config version of"); + ASSERT_REASON_CONTAINS( + status, "is no larger than the version"); + ASSERT_REASON_CONTAINS(status, "h1:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + } + + TEST_F(CheckQuorumForReconfig, QuorumCheckVetoedDueToIncompatibleSetName) { + // In this test, "we" are host "h3:1". The request to "h1" times out, + // and the request to "h2" comes back indicating an incompatible set name. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 2 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1")))); + const int myConfigIndex = 2; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h2", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 0 << "mismatch" << true))); + + Status status = checkQuorumForReconfig(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NewReplicaSetConfigurationIncompatible, status); + ASSERT_REASON_CONTAINS(status, "Our set name did not match"); + ASSERT_NOT_REASON_CONTAINS(status, "h1:1"); + ASSERT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + + } + + TEST_F(CheckQuorumForReconfig, QuorumCheckFailsDueToInsufficientVoters) { + // In this test, "we" are host "h4". Only "h1", "h2" and "h3" are voters, + // and of the voters, only "h1" responds. As a result, quorum check fails. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 2 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1" << "votes" << 0) << + BSON("_id" << 5 << "host" << "h5:1" << "votes" << 0)))); + const int myConfigIndex = 3; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + Status status = checkQuorumForReconfig(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NodeNotFound, status); + ASSERT_REASON_CONTAINS(status, "not enough voting nodes responded; required 2 but only"); + ASSERT_REASON_CONTAINS(status, "h1:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h2:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h3:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h4:1"); + ASSERT_NOT_REASON_CONTAINS(status, "h5:1"); + } + + TEST_F(CheckQuorumForReconfig, QuorumCheckFailsDueToNoElectableNodeResponding) { + // In this test, "we" are host "h4". Only "h1", "h2" and "h3" are electable, + // and none of them respond. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 2 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1" << "priority" << 0) << + BSON("_id" << 5 << "host" << "h5:1" << "priority" << 0)))); + const int myConfigIndex = 3; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + Status status = checkQuorumForReconfig(_executor.get(), rsConfig, myConfigIndex); + ASSERT_EQUALS(ErrorCodes::NodeNotFound, status); + ASSERT_REASON_CONTAINS(status, "no electable nodes responded"); + } + + // TODO: Succeed with minimal quorum. + TEST_F(CheckQuorumForReconfig, QuorumCheckSucceedsWithAsSoonAsPossible) { + // In this test, "we" are host "h4". Only "h1", "h2" and "h3" can vote. + // This test should succeed as soon as h1 and h2 respond, so we block + // h3 and h5 from responding or timing out until the test completes. + + const ReplicaSetConfig rsConfig = assertMakeRSConfig( + BSON("_id" << "rs0" << + "version" << 2 << + "members" << BSON_ARRAY( + BSON("_id" << 1 << "host" << "h1:1") << + BSON("_id" << 2 << "host" << "h2:1") << + BSON("_id" << 3 << "host" << "h3:1") << + BSON("_id" << 4 << "host" << "h4:1" << "votes" << 0) << + BSON("_id" << 5 << "host" << "h5:1" << "votes" << 0)))); + const int myConfigIndex = 3; + const BSONObj hbRequest = makeHeartbeatRequest(rsConfig, myConfigIndex); + + _net->addResponse(RemoteCommandRequest(HostAndPort("h1", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + _net->addResponse(RemoteCommandRequest(HostAndPort("h2", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 1))); + + // If this message arrived, the reconfig would be vetoed, but it is delayed + // until the quorum check completes, and so has no effect. + _net->addResponse(RemoteCommandRequest(HostAndPort("h3", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 0 << "set" << "rs0" << "v" << 3)), + true); + + // Ditto RE veto and no effect. + _net->addResponse(RemoteCommandRequest(HostAndPort("h5", 1), + "admin", + hbRequest), + StatusWith<BSONObj>(BSON("ok" << 0 << "set" << "rs0" << "v" << 3)), + true); + ASSERT_OK(checkQuorumForReconfig(_executor.get(), rsConfig, myConfigIndex)); + } + +} // namespace +} // namespace repl +} // namespace mongo |