summaryrefslogtreecommitdiff
path: root/src/mongo/dbtests/mock
diff options
context:
space:
mode:
authorRandolph Tan <randolph@10gen.com>2012-08-24 11:17:16 -0400
committerRandolph Tan <randolph@10gen.com>2012-08-30 15:30:12 -0400
commit4f56c5d9d194bac58d4a981a86bfde39cb6ffa0a (patch)
treeaa0f7f7cc9acc70de604610e752ab9b84f0944a2 /src/mongo/dbtests/mock
parent67ddf38a2b3ca17b1b84becf323d38173c97e393 (diff)
downloadmongo-4f56c5d9d194bac58d4a981a86bfde39cb6ffa0a.tar.gz
SERVER-6754 Create a mock for testing replica set connections
Step1: Create the mock classes for mocking the remote server, connection and replica set
Diffstat (limited to 'src/mongo/dbtests/mock')
-rw-r--r--src/mongo/dbtests/mock/mock_dbclient_connection.cpp137
-rw-r--r--src/mongo/dbtests/mock/mock_dbclient_connection.h99
-rw-r--r--src/mongo/dbtests/mock/mock_remote_db_server.cpp203
-rw-r--r--src/mongo/dbtests/mock/mock_remote_db_server.h174
-rw-r--r--src/mongo/dbtests/mock/mock_replica_set.cpp278
-rw-r--r--src/mongo/dbtests/mock/mock_replica_set.h131
6 files changed, 1022 insertions, 0 deletions
diff --git a/src/mongo/dbtests/mock/mock_dbclient_connection.cpp b/src/mongo/dbtests/mock/mock_dbclient_connection.cpp
new file mode 100644
index 00000000000..9cd50e00594
--- /dev/null
+++ b/src/mongo/dbtests/mock/mock_dbclient_connection.cpp
@@ -0,0 +1,137 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mongo/dbtests/mock/mock_dbclient_connection.h"
+
+#include "mongo/util/net/sock.h"
+
+using mongo::BSONObj;
+
+using std::string;
+using std::vector;
+
+namespace mongo_test {
+ MockDBClientConnection::MockDBClientConnection(MockRemoteDBServer* remoteServer):
+ _remoteServerInstanceID(remoteServer->getInstanceID()),
+ _remoteServer(remoteServer),
+ _isFailed(false) {
+ }
+
+ MockDBClientConnection::~MockDBClientConnection() {
+ }
+
+ bool MockDBClientConnection::runCommand(const string& dbname, const BSONObj& cmdObj,
+ BSONObj &info, int options, const mongo::AuthenticationTable* auth) {
+ try {
+ return _remoteServer->runCommand(_remoteServerInstanceID, dbname, cmdObj,
+ info, options, auth);
+ }
+ catch (const mongo::SocketException& exp) {
+ _isFailed = true;
+ throw;
+ }
+
+ return false;
+ }
+
+ std::auto_ptr<mongo::DBClientCursor> MockDBClientConnection::query(const string& ns,
+ mongo::Query query,
+ int nToReturn,
+ int nToSkip,
+ const BSONObj* fieldsToReturn,
+ int queryOptions,
+ int batchSize) {
+ try {
+ return _remoteServer->query(_remoteServerInstanceID, ns, query, nToReturn,
+ nToSkip, fieldsToReturn, queryOptions, batchSize);
+ }
+ catch (const mongo::SocketException& exp) {
+ _isFailed = true;
+ throw;
+ }
+
+ std::auto_ptr<mongo::DBClientCursor> nullPtr;
+ return nullPtr;
+ }
+
+ mongo::ConnectionString::ConnectionType MockDBClientConnection::type() const {
+ return mongo::ConnectionString::CUSTOM;
+ }
+
+ bool MockDBClientConnection::isFailed() const {
+ return _isFailed;
+ }
+
+ string MockDBClientConnection::getServerAddress() const {
+ return _remoteServer->getServerAddress();
+ }
+
+ string MockDBClientConnection::toString() {
+ return _remoteServer->toString();
+ }
+
+ unsigned long long MockDBClientConnection::query(boost::function<void(const BSONObj&)> f,
+ const string& ns,
+ mongo::Query query,
+ const BSONObj* fieldsToReturn,
+ int queryOptions) {
+ verify(false);
+ return 0;
+ }
+
+ unsigned long long MockDBClientConnection::query(boost::function<void(
+ mongo::DBClientCursorBatchIterator&)> f,
+ const std::string& ns,
+ mongo::Query query,
+ const mongo::BSONObj* fieldsToReturn,
+ int queryOptions) {
+ verify(false);
+ return 0;
+ }
+
+ void MockDBClientConnection::killCursor(long long cursorID) {
+ verify(false); // unimplemented
+ }
+
+ bool MockDBClientConnection::callRead(mongo::Message& toSend , mongo::Message& response) {
+ verify(false); // unimplemented
+ return false;
+ }
+
+ bool MockDBClientConnection::call(mongo::Message& toSend,
+ mongo::Message& response,
+ bool assertOk,
+ string* actualServer) {
+ verify(false); // unimplemented
+ return false;
+ }
+
+ void MockDBClientConnection::say(mongo::Message& toSend, bool isRetry, string* actualServer) {
+ verify(false); // unimplemented
+ }
+
+ void MockDBClientConnection::sayPiggyBack(mongo::Message& toSend) {
+ verify(false); // unimplemented
+ }
+
+ bool MockDBClientConnection::lazySupported() const {
+ verify(false); // unimplemented
+ return false;
+ }
+
+ double MockDBClientConnection::getSoTimeout() const {
+ return 0;
+ }
+}
diff --git a/src/mongo/dbtests/mock/mock_dbclient_connection.h b/src/mongo/dbtests/mock/mock_dbclient_connection.h
new file mode 100644
index 00000000000..7c4762fe76c
--- /dev/null
+++ b/src/mongo/dbtests/mock/mock_dbclient_connection.h
@@ -0,0 +1,99 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/dbtests/mock/mock_remote_db_server.h"
+
+namespace mongo_test {
+ /**
+ * A simple class for mocking mongo::DBClientConnection.
+ *
+ * Also check out sample usage in dbtests/mock_dbclient_conn_test.cpp
+ */
+ class MockDBClientConnection : public mongo::DBClientConnection {
+ public:
+ /**
+ * Create a mock connection to a mock server.
+ *
+ * @param remoteServer the remote server to connect to. The caller is
+ * responsible for making sure that the life of remoteServer is
+ * longer than this connection.
+ */
+ MockDBClientConnection(MockRemoteDBServer* remoteServer);
+ virtual ~MockDBClientConnection();
+
+ //
+ // DBClientBase methods
+ //
+
+ bool runCommand(const std::string& dbname, const mongo::BSONObj& cmdObj,
+ mongo::BSONObj &info, int options = 0,
+ const mongo::AuthenticationTable* auth = NULL);
+
+ std::auto_ptr<mongo::DBClientCursor> query(const std::string &ns,
+ mongo::Query query = mongo::Query(),
+ int nToReturn = 0,
+ int nToSkip = 0,
+ const mongo::BSONObj* fieldsToReturn = 0,
+ int queryOptions = 0,
+ int batchSize = 0);
+
+ //
+ // Getters
+ //
+
+ mongo::ConnectionString::ConnectionType type() const;
+ bool isFailed() const;
+ double getSoTimeout() const;
+ std::string getServerAddress() const;
+ std::string toString();
+
+ //
+ // Unsupported methods (defined to get rid of virtual function was hidden error)
+ //
+ unsigned long long query(boost::function<void(const mongo::BSONObj&)> f,
+ const std::string& ns, mongo::Query query,
+ const mongo::BSONObj* fieldsToReturn = 0, int queryOptions = 0);
+
+ unsigned long long query(boost::function<void(mongo::DBClientCursorBatchIterator&)> f,
+ const std::string& ns, mongo::Query query,
+ const mongo::BSONObj* fieldsToReturn = 0,
+ int queryOptions = 0);
+
+ //
+ // Unsupported methods (these are pure virtuals in the base class)
+ //
+
+ void killCursor(long long cursorID);
+ bool callRead(mongo::Message& toSend , mongo::Message& response);
+ bool call(mongo::Message& toSend, mongo::Message& response, bool assertOk = true,
+ std::string* actualServer = 0);
+ void say(mongo::Message& toSend, bool isRetry = false, std::string* actualServer = 0);
+ void sayPiggyBack(mongo::Message& toSend);
+ bool lazySupported() const;
+
+ private:
+ const MockRemoteDBServer::InstanceID _remoteServerInstanceID;
+ MockRemoteDBServer* _remoteServer;
+ bool _isFailed;
+ };
+}
diff --git a/src/mongo/dbtests/mock/mock_remote_db_server.cpp b/src/mongo/dbtests/mock/mock_remote_db_server.cpp
new file mode 100644
index 00000000000..dd6743dfa2e
--- /dev/null
+++ b/src/mongo/dbtests/mock/mock_remote_db_server.cpp
@@ -0,0 +1,203 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mongo/dbtests/mock/mock_remote_db_server.h"
+
+#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/net/sock.h"
+#include "mongo/util/time_support.h"
+
+using mongo::BSONObj;
+using mongo::scoped_spinlock;
+using mongo::str::stream;
+
+using std::string;
+using std::vector;
+
+namespace mongo_test {
+ MockRemoteDBServer::CircularBSONIterator::CircularBSONIterator(
+ const vector<BSONObj>& replyVector) {
+ for (std::vector<mongo::BSONObj>::const_iterator iter = replyVector.begin();
+ iter != replyVector.end(); ++iter) {
+ _replyObjs.push_back(iter->copy());
+ }
+
+ _iter = _replyObjs.begin();
+ }
+
+ BSONObj MockRemoteDBServer::CircularBSONIterator::next() {
+ verify(_iter != _replyObjs.end());
+
+ BSONObj reply = _iter->copy();
+ ++_iter;
+
+ if (_iter == _replyObjs.end()) {
+ _iter = _replyObjs.begin();
+ }
+
+ return reply;
+ }
+
+ MockRemoteDBServer::MockRemoteDBServer(const string& hostName):
+ _isRunning(true),
+ _hostName(hostName),
+ _delayMilliSec(0),
+ _cmdCount(0),
+ _queryCount(0) {
+ }
+
+ MockRemoteDBServer::~MockRemoteDBServer() {
+ }
+
+ void MockRemoteDBServer::setDelay(long long milliSec) {
+ scoped_spinlock sLock(_lock);
+ _delayMilliSec = milliSec;
+ }
+
+ void MockRemoteDBServer::shutdown() {
+ scoped_spinlock sLock(_lock);
+ _isRunning = false;
+ }
+
+ void MockRemoteDBServer::reboot() {
+ scoped_spinlock sLock(_lock);
+ _isRunning = true;
+ _instanceID++;
+ }
+
+ MockRemoteDBServer::InstanceID MockRemoteDBServer::getInstanceID() const {
+ scoped_spinlock sLock(_lock);
+ return _instanceID;
+ }
+
+ bool MockRemoteDBServer::isRunning() const {
+ scoped_spinlock sLock(_lock);
+ return _isRunning;
+ }
+
+ void MockRemoteDBServer::setCommandReply(const string& cmdName,
+ const mongo::BSONObj& replyObj) {
+ vector<BSONObj> replySequence;
+ replySequence.push_back(replyObj);
+ setCommandReply(cmdName, replySequence);
+ }
+
+ void MockRemoteDBServer::setCommandReply(const string& cmdName,
+ const vector<BSONObj>& replySequence) {
+ scoped_spinlock sLock(_lock);
+ _cmdMap[cmdName].reset(new CircularBSONIterator(replySequence));
+ }
+
+ bool MockRemoteDBServer::runCommand(MockRemoteDBServer::InstanceID id,
+ const string& dbname,
+ const BSONObj& cmdObj,
+ BSONObj &info,
+ int options,
+ const mongo::AuthenticationTable* auth) {
+ checkIfUp(id);
+
+ // Get the name of the command - copied from _runCommands @ db/dbcommands.cpp
+ BSONObj innerCmdObj;
+ {
+ mongo::BSONElement e = cmdObj.firstElement();
+ if (e.type() == mongo::Object && (e.fieldName()[0] == '$'
+ ? mongo::str::equals("query", e.fieldName()+1) :
+ mongo::str::equals("query", e.fieldName()))) {
+ innerCmdObj = e.embeddedObject();
+ }
+ else {
+ innerCmdObj = cmdObj;
+ }
+ }
+
+ string cmdName = innerCmdObj.firstElement().fieldName();
+ uassert(16430, stream() << "no reply for cmd: " << cmdName, _cmdMap.count(cmdName) == 1);
+
+ {
+ scoped_spinlock sLock(_lock);
+ info = _cmdMap[cmdName]->next();
+ }
+
+ if (_delayMilliSec > 0) {
+ mongo::sleepmillis(_delayMilliSec);
+ }
+
+ checkIfUp(id);
+
+ scoped_spinlock sLock(_lock);
+ _cmdCount++;
+ return info["ok"].trueValue();
+ }
+
+ std::auto_ptr<mongo::DBClientCursor> MockRemoteDBServer::query(
+ MockRemoteDBServer::InstanceID id,
+ const string& ns,
+ mongo::Query query,
+ int nToReturn,
+ int nToSkip,
+ const BSONObj* fieldsToReturn,
+ int queryOptions,
+ int batchSize) {
+ checkIfUp(id);
+
+ if (_delayMilliSec > 0) {
+ mongo::sleepmillis(_delayMilliSec);
+ }
+
+ checkIfUp(id);
+
+ std::auto_ptr<mongo::DBClientCursor> cursor;
+
+ scoped_spinlock sLock(_lock);
+ _queryCount++;
+ return cursor;
+ }
+
+ mongo::ConnectionString::ConnectionType MockRemoteDBServer::type() const {
+ return mongo::ConnectionString::CUSTOM;
+ }
+
+ size_t MockRemoteDBServer::getCmdCount() const {
+ scoped_spinlock sLock(_lock);
+ return _cmdCount;
+ }
+
+ size_t MockRemoteDBServer::getQueryCount() const {
+ scoped_spinlock sLock(_lock);
+ return _queryCount;
+ }
+
+ void MockRemoteDBServer::clearCounters() {
+ scoped_spinlock sLock(_lock);
+ _cmdCount = 0;
+ _queryCount = 0;
+ }
+
+ string MockRemoteDBServer::getServerAddress() const {
+ return _hostName;
+ }
+
+ string MockRemoteDBServer::toString() {
+ return _hostName;
+ }
+
+ void MockRemoteDBServer::checkIfUp(InstanceID id) const {
+ scoped_spinlock sLock(_lock);
+
+ if (!_isRunning || id < _instanceID) {
+ throw mongo::SocketException(mongo::SocketException::CLOSED, _hostName);
+ }
+ }
+}
diff --git a/src/mongo/dbtests/mock/mock_remote_db_server.h b/src/mongo/dbtests/mock/mock_remote_db_server.h
new file mode 100644
index 00000000000..741cf471fef
--- /dev/null
+++ b/src/mongo/dbtests/mock/mock_remote_db_server.h
@@ -0,0 +1,174 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/util/concurrency/spin_lock.h"
+
+namespace mongo_test {
+ /**
+ * A very simple mock that acts like a database server. Every object keeps track of its own
+ * InstanceID, which initially starts at zero and increments every time it is restarted.
+ * This is primarily used for simulating the state of which old connections won't be able
+ * to talk to the sockets that has already been closed on this server.
+ *
+ * Note: All operations on this server are protected by a lock.
+ */
+ class MockRemoteDBServer {
+ public:
+ typedef size_t InstanceID;
+
+ MockRemoteDBServer(const std::string& hostName);
+ virtual ~MockRemoteDBServer();
+
+ //
+ // Connectivity methods
+ //
+
+ /**
+ * Set a delay for calls to query and runCommand
+ */
+ void setDelay(long long milliSec);
+
+ /**
+ * Shuts down this server. Any operations on this server with an InstanceID
+ * less than or equal to the current one will throw a mongo::SocketException.
+ * To bring the server up again, use the #reboot method.
+ */
+ void shutdown();
+
+ /**
+ * Increments the instanceID of this server.
+ */
+ void reboot();
+
+ /**
+ * @return true if this server is running
+ */
+ bool isRunning() const;
+
+ //
+ // Mocking methods
+ //
+
+ /**
+ * Sets the reply for a command.
+ *
+ * @param cmdName the name of the command
+ * @param replyObj the exact reply for the command
+ */
+ void setCommandReply(const std::string& cmdName,
+ const mongo::BSONObj& replyObj);
+
+ /**
+ * Sets the reply for a command.
+ *
+ * @param cmdName the name of the command.
+ * @param replySequence the sequence of replies to cycle through every time
+ * the given command is requested. This is useful for setting up a
+ * sequence of response when the command can be called more than once
+ * that requires different results when calling a method.
+ */
+ void setCommandReply(const std::string& cmdName,
+ const std::vector<mongo::BSONObj>& replySequence);
+
+ //
+ // DBClientBase methods
+ //
+ bool runCommand(InstanceID id, const std::string& dbname,
+ const mongo::BSONObj& cmdObj,
+ mongo::BSONObj &info, int options = 0,
+ const mongo::AuthenticationTable* auth = NULL);
+
+ std::auto_ptr<mongo::DBClientCursor> query(InstanceID id,
+ const std::string &ns,
+ mongo::Query query = mongo::Query(),
+ int nToReturn = 0,
+ int nToSkip = 0,
+ const mongo::BSONObj* fieldsToReturn = 0,
+ int queryOptions = 0,
+ int batchSize = 0);
+
+ //
+ // Getters
+ //
+
+ InstanceID getInstanceID() const;
+ mongo::ConnectionString::ConnectionType type() const;
+ double getSoTimeout() const;
+ std::string getServerAddress() const;
+ std::string toString();
+
+ //
+ // Call counters
+ //
+
+ size_t getCmdCount() const;
+ size_t getQueryCount() const;
+ void clearCounters();
+
+ private:
+ /**
+ * A very simple class for cycling through a set of BSONObj
+ */
+ class CircularBSONIterator {
+ public:
+ /**
+ * Creates a new iterator with a deep copy of the vector.
+ */
+ CircularBSONIterator(const std::vector<mongo::BSONObj>& replyVector);
+ mongo::BSONObj next();
+
+ private:
+ std::vector<mongo::BSONObj>::iterator _iter;
+ std::vector<mongo::BSONObj> _replyObjs;
+ };
+
+ /**
+ * Checks whether the instance of the server is still up.
+ *
+ * @throws mongo::SocketException if this server is down
+ */
+ void checkIfUp(InstanceID id) const;
+
+ typedef std::map<std::string, boost::shared_ptr<CircularBSONIterator> > CmdToReplyObj;
+
+ bool _isRunning;
+
+ std::string _hostName;
+ long long _delayMilliSec;
+
+ CmdToReplyObj _cmdMap;
+
+ //
+ // Op Counters
+ //
+ size_t _cmdCount;
+ size_t _queryCount;
+
+ // Unique id for every restart of this server used for rejecting requests from
+ // connections that are still "connected" to the old instance
+ InstanceID _instanceID;
+
+ // protects this entire instance
+ mutable mongo::SpinLock _lock;
+ };
+}
diff --git a/src/mongo/dbtests/mock/mock_replica_set.cpp b/src/mongo/dbtests/mock/mock_replica_set.cpp
new file mode 100644
index 00000000000..d01000b2afe
--- /dev/null
+++ b/src/mongo/dbtests/mock/mock_replica_set.cpp
@@ -0,0 +1,278 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mongo/dbtests/mock/mock_replica_set.h"
+
+#include "mongo/db/repl/rs_member.h"
+#include "mongo/dbtests/mock/mock_dbclient_connection.h"
+
+#include <sstream>
+
+using mongo::BSONObjBuilder;
+
+using std::map;
+using std::string;
+using std::vector;
+
+namespace mongo_test {
+ MockReplicaSet::ReplSetConnHook::ReplSetConnHook(MockReplicaSet* replSet):
+ _replSet(replSet) {
+ }
+
+ MockReplicaSet::ReplSetConnHook::~ReplSetConnHook() {
+ }
+
+ mongo::DBClientBase* MockReplicaSet::ReplSetConnHook::connect(
+ const mongo::ConnectionString& connString,
+ std::string& errmsg,
+ double socketTimeout) {
+ vector<mongo::HostAndPort> servers = connString.getServers();
+ verify(servers.size() == 1);
+
+ const string serverName = servers.front().toString(true);
+ return new MockDBClientConnection(_replSet->getNode(serverName));
+ }
+
+ MockReplicaSet::MockReplicaSet(const string& setName, size_t nodes):
+ _setName(setName),
+ _connStringHook(this) {
+ std::vector<mongo::ReplSetConfig::MemberCfg> replConfig;
+
+ for (size_t n = 0; n < nodes; n++) {
+ std::stringstream str;
+ str << "$" << setName << n << ":27017";
+ const string hostName(str.str());
+
+ if (n == 0) {
+ _primaryHost = hostName;
+ }
+ else {
+ _secondaryHosts.push_back(hostName);
+ }
+
+ _nodeMap[hostName] = new MockRemoteDBServer(hostName);
+
+ mongo::ReplSetConfig::MemberCfg config;
+ config.h = mongo::HostAndPort(hostName);
+ replConfig.push_back(config);
+ }
+
+ setConfig(replConfig);
+ }
+
+ MockReplicaSet::~MockReplicaSet() {
+ for (std::map<string, MockRemoteDBServer*>::iterator iter = _nodeMap.begin();
+ iter != _nodeMap.end(); ++iter) {
+ delete iter->second;
+ }
+ }
+
+ string MockReplicaSet::getSetName() const {
+ return _setName;
+ }
+
+ string MockReplicaSet::getConnectionString() const {
+ std::stringstream str;
+ str << _setName;
+ str << "/";
+
+ std::map<string, MockRemoteDBServer*>::const_iterator iter = _nodeMap.begin();
+ while (iter != _nodeMap.end()) {
+ str << iter->second->getServerAddress();
+ ++iter;
+
+ if (iter != _nodeMap.end()) {
+ str << ",";
+ }
+ }
+
+ return str.str();
+ }
+
+ vector<mongo::HostAndPort> MockReplicaSet::getHosts() const {
+ vector<mongo::HostAndPort> list;
+
+ for (std::map<string, MockRemoteDBServer*>::const_iterator iter = _nodeMap.begin();
+ iter != _nodeMap.end(); ++iter) {
+ list.push_back(mongo::HostAndPort(iter->second->getServerAddress()));
+ }
+
+ return list;
+ }
+
+ string MockReplicaSet::getPrimary() const {
+ return _primaryHost;
+ }
+
+ const vector<string>& MockReplicaSet::getSecondaries() const {
+ return _secondaryHosts;
+ }
+
+ mongo::ConnectionString::ConnectionHook* MockReplicaSet::getConnectionHook() {
+ return &_connStringHook;
+ }
+
+ MockRemoteDBServer* MockReplicaSet::getNode(const string& hostName) {
+ return _nodeMap[hostName];
+ }
+
+ const vector<mongo::ReplSetConfig::MemberCfg>& MockReplicaSet::getReplConfig() const {
+ return _replConfig;
+ }
+
+ void MockReplicaSet::setConfig(const vector<mongo::ReplSetConfig::MemberCfg>& newConfig) {
+ _replConfig = newConfig;
+ mockIsMasterCmd();
+ mockReplSetGetStatusCmd();
+ }
+
+ void MockReplicaSet::kill(const string& hostName) {
+ verify(_nodeMap.count(hostName) == 1);
+ _nodeMap[hostName]->shutdown();
+ }
+
+ void MockReplicaSet::restore(const string& hostName) {
+ verify(_nodeMap.count(hostName) == 1);
+ _nodeMap[hostName]->reboot();
+ }
+
+ void MockReplicaSet::mockIsMasterCmd() {
+ // Copied from ReplSetImpl::_fillIsMaster
+ for (vector<mongo::ReplSetConfig::MemberCfg>::iterator iter = _replConfig.begin();
+ iter != _replConfig.end(); ++iter) {
+ const string hostName(iter->h.toString(true));
+ BSONObjBuilder builder;
+ builder.append("setName", _setName);
+
+ const bool isPrimary = hostName == getPrimary();
+ builder.append("ismaster", isPrimary);
+ builder.append("secondary", !isPrimary);
+
+ {
+ // TODO: add passives & arbiters
+ vector<string> hostList;
+ hostList.push_back(getPrimary());
+ for (vector<string>::const_iterator secIter = getSecondaries().begin();
+ secIter != getSecondaries().end(); ++secIter) {
+ hostList.push_back(*secIter);
+ }
+
+ builder.append("hosts", hostList);
+ }
+
+ builder.append("primary", getPrimary());
+
+ if (iter->arbiterOnly) {
+ builder.append("arbiterOnly", true);
+ }
+
+ if (iter->priority == 0 && !iter->arbiterOnly) {
+ builder.append("passive", true);
+ }
+
+ if (iter->slaveDelay) {
+ builder.append("slaveDelay", iter->slaveDelay);
+ }
+
+ if (iter->hidden) {
+ builder.append("hidden", true);
+ }
+
+ if (!iter->buildIndexes) {
+ builder.append("buildIndexes", false);
+ }
+
+ if(!iter->tags.empty()) {
+ BSONObjBuilder tagBuilder;
+ for(map<string, string>::const_iterator tagIter = iter->tags.begin();
+ tagIter != iter->tags.end(); tagIter++) {
+ tagBuilder.append(tagIter->first, tagIter->second);
+ }
+
+ builder.append("tags", tagBuilder.done());
+ }
+
+ builder.append("me", hostName);
+ builder.append("ok", true);
+
+ getNode(hostName)->setCommandReply("ismaster", builder.done());
+ }
+ }
+
+ int MockReplicaSet::getState(const std::string& host) const {
+ if (host == getPrimary()) {
+ return static_cast<int>(mongo::MemberState::RS_PRIMARY);
+ }
+ else {
+ return static_cast<int>(mongo::MemberState::RS_SECONDARY);
+ }
+ }
+
+ void MockReplicaSet::mockReplSetGetStatusCmd() {
+ // Copied from ReplSetImpl::_summarizeStatus
+ for (std::map<string, MockRemoteDBServer*>::iterator nodeIter = _nodeMap.begin();
+ nodeIter != _nodeMap.end(); ++nodeIter) {
+ MockRemoteDBServer* node = nodeIter->second;
+ vector<mongo::BSONObj> hostsField;
+
+ BSONObjBuilder fullStatBuilder;
+
+ {
+ BSONObjBuilder selfStatBuilder;
+ selfStatBuilder.append("name", node->getServerAddress());
+ selfStatBuilder.append("health", 1.0);
+ selfStatBuilder.append("state", getState(node->getServerAddress()));
+
+ selfStatBuilder.append("self", true);
+ // TODO: _id, stateStr, uptime, optime, optimeDate, maintenanceMode, errmsg
+
+ hostsField.push_back(selfStatBuilder.obj());
+ }
+
+ for (std::map<string, MockRemoteDBServer*>::iterator hostNodeIter = _nodeMap.begin();
+ hostNodeIter != _nodeMap.end(); ++hostNodeIter) {
+ MockRemoteDBServer* hostNode = hostNodeIter->second;
+
+ if (hostNode == node) {
+ continue;
+ }
+
+ BSONObjBuilder hostMemberBuilder;
+
+ // TODO: _id, stateStr, uptime, optime, optimeDate, lastHeartbeat, pingMs
+ // errmsg, authenticated
+
+ hostMemberBuilder.append("name", hostNode->getServerAddress());
+ const double health = hostNode->isRunning() ? 1.0 : 0.0;
+ hostMemberBuilder.append("health", health);
+ hostMemberBuilder.append("state", getState(hostNode->getServerAddress()));
+
+ hostsField.push_back(hostMemberBuilder.obj());
+ }
+
+ sort(hostsField.begin(), hostsField.end());
+
+ // TODO: syncingTo
+
+ fullStatBuilder.append("set", _setName);
+ fullStatBuilder.appendTimeT("date", time(0));
+ fullStatBuilder.append("myState", getState(node->getServerAddress()));
+ fullStatBuilder.append("members", hostsField);
+ fullStatBuilder.append("ok", true);
+
+ node->setCommandReply("replSetGetStatus", fullStatBuilder.done());
+ }
+ }
+}
diff --git a/src/mongo/dbtests/mock/mock_replica_set.h b/src/mongo/dbtests/mock/mock_replica_set.h
new file mode 100644
index 00000000000..4951b0eddda
--- /dev/null
+++ b/src/mongo/dbtests/mock/mock_replica_set.h
@@ -0,0 +1,131 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "mongo/dbtests/mock/mock_remote_db_server.h"
+#include "mongo/db/repl/rs_config.h"
+
+#include <string>
+#include <map>
+#include <vector>
+
+namespace mongo_test {
+ /**
+ * This is a helper class for managing a replica set consisting of
+ * MockRemoteDBServer instances.
+ *
+ * Warning: Not thread-safe
+ */
+ class MockReplicaSet {
+ public:
+ /**
+ * Creates a mock replica set and automatically mocks the isMaster
+ * and replSetGetStatus commands based on the default replica set
+ * configuration.
+ *
+ * @param setName The name for this replica set
+ * @param nodes The initial number of nodes for this replica set
+ */
+ MockReplicaSet(const std::string& setName, size_t nodes);
+ ~MockReplicaSet();
+
+ //
+ // getters
+ //
+
+ std::string getSetName() const;
+ std::string getConnectionString() const;
+ std::vector<mongo::HostAndPort> getHosts() const;
+ mongo::ConnectionString::ConnectionHook* getConnectionHook();
+ const std::vector<mongo::ReplSetConfig::MemberCfg>& getReplConfig() const;
+ std::string getPrimary() const;
+ const std::vector<std::string>& getSecondaries() const;
+
+ /**
+ * Sets the configuration for this replica sets. This also has a side effect
+ * of mocking the ismaster and replSetGetStatus command responses based on
+ * the new config.
+ */
+ void setConfig(const std::vector<mongo::ReplSetConfig::MemberCfg>& newConfig);
+
+ /**
+ * @return pointer to the mocked remote server with the given hostName.
+ * NULL if host doesn't exists.
+ */
+ MockRemoteDBServer* getNode(const std::string& hostName);
+
+ /**
+ * Kills a node.
+ *
+ * @param hostName the name of the replica node to kill.
+ */
+ void kill(const std::string& hostName);
+
+ /**
+ * Reboots a node.
+ *
+ * @param hostName the name of the host to reboot.
+ */
+ void restore(const std::string& hostName);
+
+ private:
+ class ReplSetConnHook: public mongo::ConnectionString::ConnectionHook {
+ public:
+ /**
+ * Creates a new connection hook for the ConnectionString class that
+ * can create mock connections to mock replica set members using their
+ * pseudo host names.
+ *
+ * @param replSet the mock replica set. Caller is responsible for managing
+ * replSet and making sure that it lives longer than this object.
+ */
+ ReplSetConnHook(MockReplicaSet* replSet);
+ ~ReplSetConnHook();
+
+ mongo::DBClientBase* connect(
+ const mongo::ConnectionString& connString,
+ std::string& errmsg, double socketTimeout);
+
+ private:
+ MockReplicaSet* _replSet;
+ };
+
+ /**
+ * Mocks the ismaster command based on the information on the current
+ * replica set configuration.
+ */
+ void mockIsMasterCmd();
+
+ /**
+ * Mocks the replSetGetStatus command based on the current states of the
+ * mocked servers.
+ */
+ void mockReplSetGetStatusCmd();
+
+ /**
+ * @return the replica set state of the given host
+ */
+ int getState(const std::string& host) const;
+
+ const std::string _setName;
+ std::map<std::string, MockRemoteDBServer*> _nodeMap;
+ ReplSetConnHook _connStringHook;
+ std::vector<mongo::ReplSetConfig::MemberCfg> _replConfig;
+
+ std::string _primaryHost;
+ std::vector<std::string> _secondaryHosts;
+ };
+}