/** * Copyright (C) 2008 10gen 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 . */ #include "pch.h" #include "../cmdline.h" #include "../commands.h" #include "health.h" #include "rs.h" #include "rs_config.h" #include "../dbwebserver.h" #include "../../util/mongoutils/html.h" #include "../../client/dbclient.h" using namespace bson; namespace mongo { void checkMembersUpForConfigChange(const ReplSetConfig& cfg, bool initial); /* commands in other files: replSetHeartbeat - health.cpp replSetInitiate - rs_mod.cpp */ bool replSetBlind = false; unsigned replSetForceInitialSyncFailure = 0; class CmdReplSetTest : public ReplSetCommand { public: virtual void help( stringstream &help ) const { help << "Just for regression tests.\n"; } CmdReplSetTest() : ReplSetCommand("replSetTest") { } virtual bool run(const string& , BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { log() << "replSet replSetTest command received: " << cmdObj.toString() << rsLog; if( cmdObj.hasElement("forceInitialSyncFailure") ) { replSetForceInitialSyncFailure = (unsigned) cmdObj["forceInitialSyncFailure"].Number(); return true; } // may not need this, but if removed check all tests still work: if( !check(errmsg, result) ) return false; if( cmdObj.hasElement("blind") ) { replSetBlind = cmdObj.getBoolField("blind"); return true; } return false; } } cmdReplSetTest; /** get rollback id */ class CmdReplSetGetRBID : public ReplSetCommand { public: /* todo: ideally this should only change on rollbacks NOT on mongod restarts also. fix... */ int rbid; virtual void help( stringstream &help ) const { help << "internal"; } CmdReplSetGetRBID() : ReplSetCommand("replSetGetRBID") { rbid = (int) curTimeMillis(); } virtual bool run(const string& , BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if( !check(errmsg, result) ) return false; result.append("rbid",rbid); return true; } } cmdReplSetRBID; /** we increment the rollback id on every rollback event. */ void incRBID() { cmdReplSetRBID.rbid++; } /** helper to get rollback id from another server. */ int getRBID(DBClientConnection *c) { bo info; c->simpleCommand("admin", &info, "replSetGetRBID"); return info["rbid"].numberInt(); } class CmdReplSetGetStatus : public ReplSetCommand { public: virtual void help( stringstream &help ) const { help << "Report status of a replica set from the POV of this server\n"; help << "{ replSetGetStatus : 1 }"; help << "\nhttp://www.mongodb.org/display/DOCS/Replica+Set+Commands"; } CmdReplSetGetStatus() : ReplSetCommand("replSetGetStatus", true) { } virtual bool run(const string& , BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if ( cmdObj["forShell"].trueValue() ) lastError.disableForCommand(); if( !check(errmsg, result) ) return false; theReplSet->summarizeStatus(result); return true; } } cmdReplSetGetStatus; class CmdReplSetReconfig : public ReplSetCommand { RWLock mutex; /* we don't need rw but we wanted try capability. :-( */ public: virtual void help( stringstream &help ) const { help << "Adjust configuration of a replica set\n"; help << "{ replSetReconfig : config_object }"; help << "\nhttp://www.mongodb.org/display/DOCS/Replica+Set+Commands"; } CmdReplSetReconfig() : ReplSetCommand("replSetReconfig"), mutex("rsreconfig") { } virtual bool run(const string& a, BSONObj& b, string& errmsg, BSONObjBuilder& c, bool d) { try { rwlock_try_write lk(mutex); return _run(a,b,errmsg,c,d); } catch(rwlock_try_write::exception&) { } errmsg = "a replSetReconfig is already in progress"; return false; } private: bool _run(const string& , BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if( !check(errmsg, result) ) return false; if( !theReplSet->box.getState().primary() ) { errmsg = "replSetReconfig command must be sent to the current replica set primary."; return false; } { // just make sure we can get a write lock before doing anything else. we'll reacquire one // later. of course it could be stuck then, but this check lowers the risk if weird things // are up - we probably don't want a change to apply 30 minutes after the initial attempt. time_t t = time(0); writelock lk(""); if( time(0)-t > 20 ) { errmsg = "took a long time to get write lock, so not initiating. Initiate when server less busy?"; return false; } } if( cmdObj["replSetReconfig"].type() != Object ) { errmsg = "no configuration specified"; return false; } /** TODO Support changes when a majority, but not all, members of a set are up. Determine what changes should not be allowed as they would cause erroneous states. What should be possible when a majority is not up? */ try { ReplSetConfig newConfig(cmdObj["replSetReconfig"].Obj()); log() << "replSet replSetReconfig config object parses ok, " << newConfig.members.size() << " members specified" << rsLog; if( !ReplSetConfig::legalChange(theReplSet->getConfig(), newConfig, errmsg) ) { return false; } checkMembersUpForConfigChange(newConfig,false); log() << "replSet replSetReconfig [2]" << rsLog; theReplSet->haveNewConfig(newConfig, true); ReplSet::startupStatusMsg = "replSetReconfig'd"; } catch( DBException& e ) { log() << "replSet replSetReconfig exception: " << e.what() << rsLog; throw; } return true; } } cmdReplSetReconfig; class CmdReplSetFreeze : public ReplSetCommand { public: virtual void help( stringstream &help ) const { help << "{ replSetFreeze : }"; help << "'freeze' state of member to the extent we can do that. What this really means is that\n"; help << "this node will not attempt to become primary until the time period specified expires.\n"; help << "You can call again with {replSetFreeze:0} to unfreeze sooner.\n"; help << "A process restart unfreezes the member also.\n"; help << "\nhttp://www.mongodb.org/display/DOCS/Replica+Set+Commands"; } CmdReplSetFreeze() : ReplSetCommand("replSetFreeze") { } virtual bool run(const string& , BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if( !check(errmsg, result) ) return false; int secs = (int) cmdObj.firstElement().numberInt(); if( theReplSet->freeze(secs) ) { if( secs == 0 ) result.append("info","unfreezing"); } if( secs == 1 ) result.append("warning", "you really want to freeze for only 1 second?"); return true; } } cmdReplSetFreeze; class CmdReplSetStepDown: public ReplSetCommand { public: virtual void help( stringstream &help ) const { help << "{ replSetStepDown : }\n"; help << "Step down as primary. Will not try to reelect self for the specified time period (1 minute if no numeric secs value specified).\n"; help << "(If another member with same priority takes over in the meantime, it will stay primary.)\n"; help << "http://www.mongodb.org/display/DOCS/Replica+Set+Commands"; } CmdReplSetStepDown() : ReplSetCommand("replSetStepDown") { } virtual bool run(const string& , BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if( !check(errmsg, result) ) return false; if( !theReplSet->box.getState().primary() ) { errmsg = "not primary so can't step down"; return false; } int secs = (int) cmdObj.firstElement().numberInt(); if( secs == 0 ) secs = 60; return theReplSet->stepDown(secs); } } cmdReplSetStepDown; using namespace bson; using namespace mongoutils::html; extern void fillRsLog(stringstream&); class ReplSetHandler : public DbWebHandler { public: ReplSetHandler() : DbWebHandler( "_replSet" , 1 , true ) {} virtual bool handles( const string& url ) const { return startsWith( url , "/_replSet" ); } virtual void handle( const char *rq, string url, BSONObj params, string& responseMsg, int& responseCode, vector& headers, const SockAddr &from ) { if( url == "/_replSetOplog" ) { responseMsg = _replSetOplog(params); } else responseMsg = _replSet(); responseCode = 200; } string _replSetOplog(bo parms) { int _id = (int) str::toUnsigned( parms["_id"].String() ); stringstream s; string t = "Replication oplog"; s << start(t); s << p(t); if( theReplSet == 0 ) { if( cmdLine._replSet.empty() ) s << p("Not using --replSet"); else { s << p("Still starting up, or else set is not yet " + a("http://www.mongodb.org/display/DOCS/Replica+Set+Configuration#InitialSetup", "", "initiated") + ".
" + ReplSet::startupStatusMsg.get()); } } else { try { theReplSet->getOplogDiagsAsHtml(_id, s); } catch(std::exception& e) { s << "error querying oplog: " << e.what() << '\n'; } } s << _end(); return s.str(); } /* /_replSet show replica set status in html format */ string _replSet() { stringstream s; s << start("Replica Set Status " + prettyHostName()); s << p( a("/", "back", "Home") + " | " + a("/local/system.replset/?html=1", "", "View Replset Config") + " | " + a("/replSetGetStatus?text=1", "", "replSetGetStatus") + " | " + a("http://www.mongodb.org/display/DOCS/Replica+Sets", "", "Docs") ); if( theReplSet == 0 ) { if( cmdLine._replSet.empty() ) s << p("Not using --replSet"); else { s << p("Still starting up, or else set is not yet " + a("http://www.mongodb.org/display/DOCS/Replica+Set+Configuration#InitialSetup", "", "initiated") + ".
" + ReplSet::startupStatusMsg.get()); } } else { try { theReplSet->summarizeAsHtml(s); } catch(...) { s << "error summarizing replset status\n"; } } s << p("Recent replset log activity:"); fillRsLog(s); s << _end(); return s.str(); } } replSetHandler; }