diff options
Diffstat (limited to 'db/repl/rs_config.cpp')
-rw-r--r-- | db/repl/rs_config.cpp | 174 |
1 files changed, 124 insertions, 50 deletions
diff --git a/db/repl/rs_config.cpp b/db/repl/rs_config.cpp index 371507d..5998f51 100644 --- a/db/repl/rs_config.cpp +++ b/db/repl/rs_config.cpp @@ -27,11 +27,11 @@ using namespace bson; -namespace mongo { +namespace mongo { void logOpInitiate(const bo&); - void assertOnlyHas(BSONObj o, const set<string>& fields) { + void assertOnlyHas(BSONObj o, const set<string>& fields) { BSONObj::iterator i(o); while( i.more() ) { BSONElement e = i.next(); @@ -41,7 +41,7 @@ namespace mongo { } } - list<HostAndPort> ReplSetConfig::otherMemberHostnames() const { + list<HostAndPort> ReplSetConfig::otherMemberHostnames() const { list<HostAndPort> L; for( vector<MemberCfg>::const_iterator i = members.begin(); i != members.end(); i++ ) { if( !i->h.isSelf() ) @@ -49,12 +49,12 @@ namespace mongo { } return L; } - + /* comment MUST only be set when initiating the set by the initiator */ - void ReplSetConfig::saveConfigLocally(bo comment) { + void ReplSetConfig::saveConfigLocally(bo comment) { checkRsConfig(); log() << "replSet info saving a newer config version to local.system.replset" << rsLog; - { + { writelock lk(""); Client::Context cx( rsConfigNs ); cx.db()->flushFiles(true); @@ -70,21 +70,21 @@ namespace mongo { } DEV log() << "replSet saveConfigLocally done" << rsLog; } - - /*static*/ - /*void ReplSetConfig::receivedNewConfig(BSONObj cfg) { + + /*static*/ + /*void ReplSetConfig::receivedNewConfig(BSONObj cfg) { if( theReplSet ) return; // this is for initial setup only, so far. todo ReplSetConfig c(cfg); writelock lk("admin."); - if( theReplSet ) + if( theReplSet ) return; c.saveConfigLocally(bo()); }*/ - bo ReplSetConfig::MemberCfg::asBson() const { + bo ReplSetConfig::MemberCfg::asBson() const { bob b; b << "_id" << _id; b.append("host", h.toString()); @@ -93,18 +93,28 @@ namespace mongo { if( arbiterOnly ) b << "arbiterOnly" << true; if( slaveDelay ) b << "slaveDelay" << slaveDelay; if( hidden ) b << "hidden" << hidden; + if( !buildIndexes ) b << "buildIndexes" << buildIndexes; + if( !tags.empty() ) { + BSONArrayBuilder a; + for( set<string>::const_iterator i = tags.begin(); i != tags.end(); i++ ) + a.append(*i); + b.appendArray("tags", a.done()); + } + if( !initialSync.isEmpty() ) { + b << "initialSync" << initialSync; + } return b.obj(); } - bo ReplSetConfig::asBson() const { + bo ReplSetConfig::asBson() const { bob b; b.append("_id", _id).append("version", version); if( !ho.isDefault() || !getLastErrorDefaults.isEmpty() ) { bob settings; if( !ho.isDefault() ) - settings << "heartbeatConnRetries " << ho.heartbeatConnRetries << - "heartbeatSleep" << ho.heartbeatSleepMillis / 1000 << - "heartbeatTimeout" << ho.heartbeatTimeoutMillis / 1000; + settings << "heartbeatConnRetries " << ho.heartbeatConnRetries << + "heartbeatSleep" << ho.heartbeatSleepMillis / 1000.0 << + "heartbeatTimeout" << ho.heartbeatTimeoutMillis / 1000.0; if( !getLastErrorDefaults.isEmpty() ) settings << "getLastErrorDefaults" << getLastErrorDefaults; b << "settings" << settings.obj(); @@ -122,7 +132,7 @@ namespace mongo { uassert(13126, "bad Member config", expr); } - void ReplSetConfig::MemberCfg::check() const{ + void ReplSetConfig::MemberCfg::check() const { mchk(_id >= 0 && _id <= 255); mchk(priority >= 0 && priority <= 1000); mchk(votes >= 0 && votes <= 100); @@ -130,41 +140,80 @@ namespace mongo { uassert(13437, "slaveDelay requires priority be zero", slaveDelay == 0 || priority == 0); uassert(13438, "bad slaveDelay value", slaveDelay >= 0 && slaveDelay <= 3600 * 24 * 366); uassert(13439, "priority must be 0 when hidden=true", priority == 0 || !hidden); + uassert(13477, "priority must be 0 when buildIndexes=false", buildIndexes || priority == 0); + + if (!initialSync.isEmpty()) { + static const string legal[] = {"state", "name", "_id","optime"}; + static const set<string> legals(legal, legal + 4); + assertOnlyHas(initialSync, legals); + + if (initialSync.hasElement("state")) { + uassert(13525, "initialSync source state must be 1 or 2", + initialSync["state"].isNumber() && + (initialSync["state"].Number() == 1 || + initialSync["state"].Number() == 2)); + } + if (initialSync.hasElement("name")) { + uassert(13526, "initialSync source name must be a string", + initialSync["name"].type() == mongo::String); + } + if (initialSync.hasElement("_id")) { + uassert(13527, "initialSync source _id must be a number", + initialSync["_id"].isNumber()); + } + if (initialSync.hasElement("optime")) { + uassert(13528, "initialSync source optime must be a timestamp", + initialSync["optime"].type() == mongo::Timestamp || + initialSync["optime"].type() == mongo::Date); + } + } } /** @param o old config - @param n new config + @param n new config */ - /*static*/ bool ReplSetConfig::legalChange(const ReplSetConfig& o, const ReplSetConfig& n, string& errmsg) { + /*static*/ + bool ReplSetConfig::legalChange(const ReplSetConfig& o, const ReplSetConfig& n, string& errmsg) { assert( theReplSet ); - if( o._id != n._id ) { - errmsg = "set name may not change"; + if( o._id != n._id ) { + errmsg = "set name may not change"; return false; } /* TODO : wonder if we need to allow o.version < n.version only, which is more lenient. - if someone had some intermediate config this node doesnt have, that could be + if someone had some intermediate config this node doesnt have, that could be necessary. but then how did we become primary? so perhaps we are fine as-is. */ - if( o.version + 1 != n.version ) { + if( o.version + 1 != n.version ) { errmsg = "version number wrong"; return false; } map<HostAndPort,const ReplSetConfig::MemberCfg*> old; - for( vector<ReplSetConfig::MemberCfg>::const_iterator i = o.members.begin(); i != o.members.end(); i++ ) { + for( vector<ReplSetConfig::MemberCfg>::const_iterator i = o.members.begin(); i != o.members.end(); i++ ) { old[i->h] = &(*i); } int me = 0; - for( vector<ReplSetConfig::MemberCfg>::const_iterator i = n.members.begin(); i != n.members.end(); i++ ) { + for( vector<ReplSetConfig::MemberCfg>::const_iterator i = n.members.begin(); i != n.members.end(); i++ ) { const ReplSetConfig::MemberCfg& m = *i; - if( old.count(m.h) ) { - if( old[m.h]->_id != m._id ) { + if( old.count(m.h) ) { + const ReplSetConfig::MemberCfg& oldCfg = *old[m.h]; + if( oldCfg._id != m._id ) { log() << "replSet reconfig error with member: " << m.h.toString() << rsLog; uasserted(13432, "_id may not change for members"); } + if( oldCfg.buildIndexes != m.buildIndexes ) { + log() << "replSet reconfig error with member: " << m.h.toString() << rsLog; + uasserted(13476, "buildIndexes may not change for members"); + } + /* are transitions to and from arbiterOnly guaranteed safe? if not, we should disallow here. + there is a test at replsets/replsetarb3.js */ + if( oldCfg.arbiterOnly != m.arbiterOnly ) { + log() << "replSet reconfig error with member: " << m.h.toString() << " arbiterOnly cannot change. remove and readd the member instead " << rsLog; + uasserted(13510, "arbiterOnly may not change for members"); + } } - if( m.h.isSelf() ) + if( m.h.isSelf() ) me++; } @@ -172,24 +221,33 @@ namespace mongo { /* TODO : MORE CHECKS HERE */ - log() << "replSet TODO : don't allow removal of a node until we handle it at the removed node end?" << endl; + DEV log() << "replSet TODO : don't allow removal of a node until we handle it at the removed node end?" << endl; // we could change its votes to zero perhaps instead as a short term... return true; } - void ReplSetConfig::clear() { + void ReplSetConfig::clear() { version = -5; _ok = false; } - void ReplSetConfig::checkRsConfig() const { + void ReplSetConfig::checkRsConfig() const { uassert(13132, - "nonmatching repl set name in _id field; check --replSet command line", - _id == cmdLine.ourSetName()); + "nonmatching repl set name in _id field; check --replSet command line", + _id == cmdLine.ourSetName()); uassert(13308, "replSet bad config version #", version > 0); uassert(13133, "replSet bad config no members", members.size() >= 1); - uassert(13309, "replSet bad config maximum number of members is 7 (for now)", members.size() <= 7); + uassert(13309, "replSet bad config maximum number of members is 12", members.size() <= 12); + { + unsigned voters = 0; + for( vector<MemberCfg>::const_iterator i = members.begin(); i != members.end(); ++i ) { + if( i->votes ) + voters++; + } + uassert(13612, "replSet bad config maximum number of voting members is 7", voters <= 7); + uassert(13613, "replSet bad config no voting members", voters > 0); + } } void ReplSetConfig::from(BSONObj o) { @@ -213,7 +271,8 @@ namespace mongo { if( settings["heartbeatTimeout"].ok() ) ho.heartbeatTimeoutMillis = (unsigned) (settings["heartbeatTimeout"].Number() * 1000); ho.check(); - try { getLastErrorDefaults = settings["getLastErrorDefaults"].Obj().copy(); } catch(...) { } + try { getLastErrorDefaults = settings["getLastErrorDefaults"].Obj().copy(); } + catch(...) { } } set<string> hosts; @@ -231,43 +290,57 @@ namespace mongo { BSONObj mobj = members[i].Obj(); MemberCfg m; try { - static const string legal[] = {"_id","votes","priority","host","hidden","slaveDelay","arbiterOnly"}; - static const set<string> legals(legal, legal + 7); + static const string legal[] = { + "_id","votes","priority","host", "hidden","slaveDelay", + "arbiterOnly","buildIndexes","tags","initialSync" + }; + static const set<string> legals(legal, legal + 10); assertOnlyHas(mobj, legals); - try { + try { m._id = (int) mobj["_id"].Number(); - } catch(...) { + } + catch(...) { /* TODO: use of string exceptions may be problematic for reconfig case! */ - throw "_id must be numeric"; + throw "_id must be numeric"; } string s; try { s = mobj["host"].String(); m.h = HostAndPort(s); } - catch(...) { + catch(...) { throw string("bad or missing host field? ") + mobj.toString(); } - if( m.h.isLocalHost() ) + if( m.h.isLocalHost() ) localhosts++; m.arbiterOnly = mobj.getBoolField("arbiterOnly"); m.slaveDelay = mobj["slaveDelay"].numberInt(); if( mobj.hasElement("hidden") ) m.hidden = mobj.getBoolField("hidden"); + if( mobj.hasElement("buildIndexes") ) + m.buildIndexes = mobj.getBoolField("buildIndexes"); if( mobj.hasElement("priority") ) m.priority = mobj["priority"].Number(); if( mobj.hasElement("votes") ) m.votes = (unsigned) mobj["votes"].Number(); + if( mobj.hasElement("tags") ) { + vector<BSONElement> v = mobj["tags"].Array(); + for( unsigned i = 0; i < v.size(); i++ ) + m.tags.insert( v[i].String() ); + } + if( mobj.hasElement("initialSync")) { + m.initialSync = mobj["initialSync"].Obj().getOwned(); + } m.check(); } - catch( const char * p ) { + catch( const char * p ) { log() << "replSet cfg parsing exception for members[" << i << "] " << p << rsLog; stringstream ss; ss << "replSet members[" << i << "] " << p; uassert(13107, ss.str(), false); } - catch(DBException& e) { + catch(DBException& e) { log() << "replSet cfg parsing exception for members[" << i << "] " << e.what() << rsLog; stringstream ss; ss << "bad config for member[" << i << "] " << e.what(); @@ -289,7 +362,7 @@ namespace mongo { uassert(13122, "bad repl set config?", expr); } - ReplSetConfig::ReplSetConfig(BSONObj cfg) { + ReplSetConfig::ReplSetConfig(BSONObj cfg) { clear(); from(cfg); configAssert( version < 0 /*unspecified*/ || (version >= 1 && version <= 5000) ); @@ -315,18 +388,19 @@ namespace mongo { BSONObj cmd = BSON( "replSetHeartbeat" << setname ); int theirVersion; BSONObj info; + log() << "trying to contact " << h.toString() << rsLog; bool ok = requestHeartbeat(setname, "", h.toString(), info, -2, theirVersion); - if( info["rs"].trueValue() ) { + if( info["rs"].trueValue() ) { // yes, it is a replicate set, although perhaps not yet initialized } else { if( !ok ) { log() << "replSet TEMP !ok heartbeating " << h.toString() << " on cfg load" << rsLog; - if( !info.isEmpty() ) + if( !info.isEmpty() ) log() << "replSet info " << h.toString() << " : " << info.toString() << rsLog; return; } - { + { stringstream ss; ss << "replSet error: member " << h.toString() << " is not in --replSet mode"; msgassertedNoTrace(13260, ss.str().c_str()); // not caught as not a user exception - we want it not caught @@ -343,7 +417,7 @@ namespace mongo { cfg = conn.findOne(rsConfigNs, Query()).getOwned(); count = conn.count(rsConfigNs); } - catch ( DBException& e) { + catch ( DBException& ) { if ( !h.isSelf() ) { throw; } @@ -356,14 +430,14 @@ namespace mongo { if( count > 1 ) uasserted(13109, str::stream() << "multiple rows in " << rsConfigNs << " not supported host: " << h.toString()); - + if( cfg.isEmpty() ) { version = EMPTYCONFIG; return; } version = -1; } - catch( DBException& e) { + catch( DBException& e) { version = v; log(level) << "replSet load config couldn't get from " << h.toString() << ' ' << e.what() << rsLog; return; |