diff options
author | Randolph Tan <randolph@10gen.com> | 2012-08-24 11:17:16 -0400 |
---|---|---|
committer | Randolph Tan <randolph@10gen.com> | 2012-08-30 15:30:12 -0400 |
commit | 4f56c5d9d194bac58d4a981a86bfde39cb6ffa0a (patch) | |
tree | aa0f7f7cc9acc70de604610e752ab9b84f0944a2 /src/mongo/dbtests/mock | |
parent | 67ddf38a2b3ca17b1b84becf323d38173c97e393 (diff) | |
download | mongo-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.cpp | 137 | ||||
-rw-r--r-- | src/mongo/dbtests/mock/mock_dbclient_connection.h | 99 | ||||
-rw-r--r-- | src/mongo/dbtests/mock/mock_remote_db_server.cpp | 203 | ||||
-rw-r--r-- | src/mongo/dbtests/mock/mock_remote_db_server.h | 174 | ||||
-rw-r--r-- | src/mongo/dbtests/mock/mock_replica_set.cpp | 278 | ||||
-rw-r--r-- | src/mongo/dbtests/mock/mock_replica_set.h | 131 |
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; + }; +} |