summaryrefslogtreecommitdiff
path: root/src/mongo/s/commands_admin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/s/commands_admin.cpp')
-rw-r--r--src/mongo/s/commands_admin.cpp1239
1 files changed, 1239 insertions, 0 deletions
diff --git a/src/mongo/s/commands_admin.cpp b/src/mongo/s/commands_admin.cpp
new file mode 100644
index 00000000000..cbe306f47a8
--- /dev/null
+++ b/src/mongo/s/commands_admin.cpp
@@ -0,0 +1,1239 @@
+// s/commands_admin.cpp
+
+/**
+* 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 <http://www.gnu.org/licenses/>.
+*/
+
+/* TODO
+ _ concurrency control.
+ _ limit() works right?
+ _ KillCursors
+
+ later
+ _ secondary indexes
+*/
+
+#include "pch.h"
+#include "../util/net/message.h"
+#include "../util/net/listen.h"
+#include "../util/processinfo.h"
+#include "../util/stringutils.h"
+#include "../util/version.h"
+#include "../util/timer.h"
+
+#include "../client/connpool.h"
+
+#include "../db/dbmessage.h"
+#include "../db/commands.h"
+#include "../db/stats/counters.h"
+
+#include "config.h"
+#include "chunk.h"
+#include "grid.h"
+#include "strategy.h"
+#include "stats.h"
+#include "writeback_listener.h"
+#include "client.h"
+#include "../util/ramlog.h"
+
+namespace mongo {
+
+ namespace dbgrid_cmds {
+
+ class GridAdminCmd : public Command {
+ public:
+ GridAdminCmd( const char * n ) : Command( n , false, tolowerString(n).c_str() ) {
+ }
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ // all grid commands are designed not to lock
+ virtual LockType locktype() const { return NONE; }
+
+ bool okForConfigChanges( string& errmsg ) {
+ string e;
+ if ( ! configServer.allUp(e) ) {
+ errmsg = str::stream() << "not all config servers are up: " << e;
+ return false;
+ }
+ return true;
+ }
+ };
+
+ // --------------- misc commands ----------------------
+
+ class NetStatCmd : public GridAdminCmd {
+ public:
+ NetStatCmd() : GridAdminCmd("netstat") { }
+ virtual void help( stringstream& help ) const {
+ help << " shows status/reachability of servers in the cluster";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ result.append("configserver", configServer.getPrimary().getConnString() );
+ result.append("isdbgrid", 1);
+ return true;
+ }
+ } netstat;
+
+ class FlushRouterConfigCmd : public GridAdminCmd {
+ public:
+ FlushRouterConfigCmd() : GridAdminCmd("flushRouterConfig") { }
+ virtual void help( stringstream& help ) const {
+ help << "flush all router config";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ grid.flushConfig();
+ result.appendBool( "flushed" , true );
+ return true;
+ }
+ } flushRouterConfigCmd;
+
+
+ class ServerStatusCmd : public Command {
+ public:
+ ServerStatusCmd() : Command( "serverStatus" , true ) {
+ _started = time(0);
+ }
+
+ virtual bool slaveOk() const { return true; }
+ virtual LockType locktype() const { return NONE; }
+
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
+ result.append( "host" , prettyHostName() );
+ result.append("version", versionString);
+ result.append("process","mongos");
+ result.append("uptime",(double) (time(0)-_started));
+ result.appendDate( "localTime" , jsTime() );
+
+ {
+ BSONObjBuilder t( result.subobjStart( "mem" ) );
+
+ ProcessInfo p;
+ if ( p.supported() ) {
+ t.appendNumber( "resident" , p.getResidentSize() );
+ t.appendNumber( "virtual" , p.getVirtualMemorySize() );
+ t.appendBool( "supported" , true );
+ }
+ else {
+ result.append( "note" , "not all mem info support on this platform" );
+ t.appendBool( "supported" , false );
+ }
+
+ t.done();
+ }
+
+ {
+ BSONObjBuilder bb( result.subobjStart( "connections" ) );
+ bb.append( "current" , connTicketHolder.used() );
+ bb.append( "available" , connTicketHolder.available() );
+ bb.done();
+ }
+
+ {
+ BSONObjBuilder bb( result.subobjStart( "extra_info" ) );
+ bb.append("note", "fields vary by platform");
+ ProcessInfo p;
+ p.getExtraInfo(bb);
+ bb.done();
+ }
+
+ result.append( "opcounters" , globalOpCounters.getObj() );
+ {
+ BSONObjBuilder bb( result.subobjStart( "ops" ) );
+ bb.append( "sharded" , opsSharded.getObj() );
+ bb.append( "notSharded" , opsNonSharded.getObj() );
+ bb.done();
+ }
+
+ result.append( "shardCursorType" , shardedCursorTypes.getObj() );
+
+ {
+ BSONObjBuilder asserts( result.subobjStart( "asserts" ) );
+ asserts.append( "regular" , assertionCount.regular );
+ asserts.append( "warning" , assertionCount.warning );
+ asserts.append( "msg" , assertionCount.msg );
+ asserts.append( "user" , assertionCount.user );
+ asserts.append( "rollovers" , assertionCount.rollovers );
+ asserts.done();
+ }
+
+ {
+ BSONObjBuilder bb( result.subobjStart( "network" ) );
+ networkCounter.append( bb );
+ bb.done();
+ }
+
+ {
+ RamLog* rl = RamLog::get( "warnings" );
+ verify(15879, rl);
+
+ if (rl->lastWrite() >= time(0)-(10*60)){ // only show warnings from last 10 minutes
+ vector<const char*> lines;
+ rl->get( lines );
+
+ BSONArrayBuilder arr( result.subarrayStart( "warnings" ) );
+ for ( unsigned i=std::max(0,(int)lines.size()-10); i<lines.size(); i++ )
+ arr.append( lines[i] );
+ arr.done();
+ }
+ }
+
+ return 1;
+ }
+
+ time_t _started;
+ } cmdServerStatus;
+
+ class FsyncCommand : public GridAdminCmd {
+ public:
+ FsyncCommand() : GridAdminCmd( "fsync" ) {}
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ if ( cmdObj["lock"].trueValue() ) {
+ errmsg = "can't do lock through mongos";
+ return false;
+ }
+
+ BSONObjBuilder sub;
+
+ bool ok = true;
+ int numFiles = 0;
+
+ vector<Shard> shards;
+ Shard::getAllShards( shards );
+ for ( vector<Shard>::iterator i=shards.begin(); i!=shards.end(); i++ ) {
+ Shard s = *i;
+
+ BSONObj x = s.runCommand( "admin" , "fsync" );
+ sub.append( s.getName() , x );
+
+ if ( ! x["ok"].trueValue() ) {
+ ok = false;
+ errmsg = x["errmsg"].String();
+ }
+
+ numFiles += x["numFiles"].numberInt();
+ }
+
+ result.append( "numFiles" , numFiles );
+ result.append( "all" , sub.obj() );
+ return ok;
+ }
+ } fsyncCmd;
+
+ // ------------ database level commands -------------
+
+ class MoveDatabasePrimaryCommand : public GridAdminCmd {
+ public:
+ MoveDatabasePrimaryCommand() : GridAdminCmd("movePrimary") { }
+ virtual void help( stringstream& help ) const {
+ help << " example: { moveprimary : 'foo' , to : 'localhost:9999' }";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ string dbname = cmdObj.firstElement().valuestrsafe();
+
+ if ( dbname.size() == 0 ) {
+ errmsg = "no db";
+ return false;
+ }
+
+ if ( dbname == "config" ) {
+ errmsg = "can't move config db";
+ return false;
+ }
+
+ DBConfigPtr config = grid.getDBConfig( dbname , false );
+ if ( ! config ) {
+ errmsg = "can't find db!";
+ return false;
+ }
+
+ string to = cmdObj["to"].valuestrsafe();
+ if ( ! to.size() ) {
+ errmsg = "you have to specify where you want to move it";
+ return false;
+ }
+ Shard s = Shard::make( to );
+
+ if ( config->getPrimary() == s.getConnString() ) {
+ errmsg = "it is already the primary";
+ return false;
+ }
+
+ if ( ! grid.knowAboutShard( s.getConnString() ) ) {
+ errmsg = "that server isn't known to me";
+ return false;
+ }
+
+ log() << "Moving " << dbname << " primary from: " << config->getPrimary().toString()
+ << " to: " << s.toString() << endl;
+
+ // Locking enabled now...
+ DistributedLock lockSetup( configServer.getConnectionString(), dbname + "-movePrimary" );
+ dist_lock_try dlk;
+
+ // Distributed locking added.
+ try{
+ dlk = dist_lock_try( &lockSetup , string("Moving primary shard of ") + dbname );
+ }
+ catch( LockException& e ){
+ errmsg = str::stream() << "error locking distributed lock to move primary shard of " << dbname << causedBy( e );
+ warning() << errmsg << endl;
+ return false;
+ }
+
+ if ( ! dlk.got() ) {
+ errmsg = (string)"metadata lock is already taken for moving " + dbname;
+ return false;
+ }
+
+ ScopedDbConnection toconn( s.getConnString() );
+
+ // TODO ERH - we need a clone command which replays operations from clone start to now
+ // can just use local.oplog.$main
+ BSONObj cloneRes;
+ bool worked = toconn->runCommand( dbname.c_str() , BSON( "clone" << config->getPrimary().getConnString() ) , cloneRes );
+ toconn.done();
+
+ if ( ! worked ) {
+ log() << "clone failed" << cloneRes << endl;
+ errmsg = "clone failed";
+ return false;
+ }
+
+ ScopedDbConnection fromconn( config->getPrimary() );
+
+ config->setPrimary( s.getConnString() );
+
+ log() << "movePrimary: dropping " << dbname << " from old" << endl;
+
+ fromconn->dropDatabase( dbname.c_str() );
+ fromconn.done();
+
+ result << "primary " << s.toString();
+
+ return true;
+ }
+ } movePrimary;
+
+ class EnableShardingCmd : public GridAdminCmd {
+ public:
+ EnableShardingCmd() : GridAdminCmd( "enableSharding" ) {}
+ virtual void help( stringstream& help ) const {
+ help
+ << "Enable sharding for a db. (Use 'shardcollection' command afterwards.)\n"
+ << " { enablesharding : \"<dbname>\" }\n";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ string dbname = cmdObj.firstElement().valuestrsafe();
+ if ( dbname.size() == 0 ) {
+ errmsg = "no db";
+ return false;
+ }
+
+ if ( dbname == "admin" ) {
+ errmsg = "can't shard the admin db";
+ return false;
+ }
+ if ( dbname == "local" ) {
+ errmsg = "can't shard the local db";
+ return false;
+ }
+
+ DBConfigPtr config = grid.getDBConfig( dbname );
+ if ( config->isShardingEnabled() ) {
+ errmsg = "already enabled";
+ return false;
+ }
+
+ if ( ! okForConfigChanges( errmsg ) )
+ return false;
+
+ log() << "enabling sharding on: " << dbname << endl;
+
+ config->enableSharding();
+
+ return true;
+ }
+ } enableShardingCmd;
+
+ // ------------ collection level commands -------------
+
+ class ShardCollectionCmd : public GridAdminCmd {
+ public:
+ ShardCollectionCmd() : GridAdminCmd( "shardCollection" ) {}
+
+ virtual void help( stringstream& help ) const {
+ help
+ << "Shard a collection. Requires key. Optional unique. Sharding must already be enabled for the database.\n"
+ << " { enablesharding : \"<dbname>\" }\n";
+ }
+
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ string ns = cmdObj.firstElement().valuestrsafe();
+ if ( ns.size() == 0 ) {
+ errmsg = "no ns";
+ return false;
+ }
+
+ DBConfigPtr config = grid.getDBConfig( ns );
+ if ( ! config->isShardingEnabled() ) {
+ errmsg = "sharding not enabled for db";
+ return false;
+ }
+
+ if ( config->isSharded( ns ) ) {
+ errmsg = "already sharded";
+ return false;
+ }
+
+ BSONObj key = cmdObj.getObjectField( "key" );
+ if ( key.isEmpty() ) {
+ errmsg = "no shard key";
+ return false;
+ }
+
+ BSONForEach(e, key) {
+ if (!e.isNumber() || e.number() != 1.0) {
+ errmsg = "shard keys must all be ascending";
+ return false;
+ }
+ }
+
+ if ( ns.find( ".system." ) != string::npos ) {
+ errmsg = "can't shard system namespaces";
+ return false;
+ }
+
+ if ( ! okForConfigChanges( errmsg ) )
+ return false;
+
+ // Sharding interacts with indexing in at least three ways:
+ //
+ // 1. A unique index must have the sharding key as its prefix. Otherwise maintaining uniqueness would
+ // require coordinated access to all shards. Trying to shard a collection with such an index is not
+ // allowed.
+ //
+ // 2. Sharding a collection requires an index over the sharding key. That index must be create upfront.
+ // The rationale is that sharding a non-empty collection would need to create the index and that could
+ // be slow. Requiring the index upfront allows the admin to plan before sharding and perhaps use
+ // background index construction. One exception to the rule: empty collections. It's fairly easy to
+ // create the index as part of the sharding process.
+ //
+ // 3. If unique : true is specified, we require that the sharding index be unique or created as unique.
+ //
+ // We enforce both these conditions in what comes next.
+
+ bool careAboutUnique = cmdObj["unique"].trueValue();
+
+ {
+ ShardKeyPattern proposedKey( key );
+ bool hasShardIndex = false;
+ bool hasUniqueShardIndex = false;
+
+ ScopedDbConnection conn( config->getPrimary() );
+ BSONObjBuilder b;
+ b.append( "ns" , ns );
+
+ BSONArrayBuilder allIndexes;
+
+ auto_ptr<DBClientCursor> cursor = conn->query( config->getName() + ".system.indexes" , b.obj() );
+ while ( cursor->more() ) {
+ BSONObj idx = cursor->next();
+
+ allIndexes.append( idx );
+
+ bool idIndex = ! idx["name"].eoo() && idx["name"].String() == "_id_";
+ bool uniqueIndex = ( ! idx["unique"].eoo() && idx["unique"].trueValue() ) ||
+ idIndex;
+
+ // Is index key over the sharding key? Remember that.
+ if ( key.woCompare( idx["key"].embeddedObjectUserCheck() ) == 0 ) {
+
+ if( idx["sparse"].trueValue() ){
+ errmsg = (string)"can't shard collection " + ns + " with sparse shard key index";
+ conn.done();
+ return false;
+ }
+
+ hasShardIndex = true;
+ hasUniqueShardIndex = uniqueIndex;
+ continue;
+ }
+
+ // Not a unique index? Move on.
+ if ( ! uniqueIndex || idIndex )
+ continue;
+
+ // Shard key is prefix of unique index? Move on.
+ if ( proposedKey.isPrefixOf( idx["key"].embeddedObjectUserCheck() ) )
+ continue;
+
+ errmsg = str::stream() << "can't shard collection '" << ns << "' with unique index on: " + idx.toString()
+ << ", uniqueness can't be maintained across unless shard key index is a prefix";
+ conn.done();
+ return false;
+ }
+
+ if( careAboutUnique && hasShardIndex && ! hasUniqueShardIndex ){
+ errmsg = (string)"can't shard collection " + ns + ", shard key index not unique and unique index explicitly specified";
+ conn.done();
+ return false;
+ }
+
+ BSONObj res = conn->findOne( config->getName() + ".system.namespaces" , BSON( "name" << ns ) );
+ if ( res["options"].type() == Object && res["options"].embeddedObject()["capped"].trueValue() ) {
+ errmsg = "can't shard capped collection";
+ conn.done();
+ return false;
+ }
+
+ if ( hasShardIndex ) {
+ // make sure there are no null entries in the sharding index
+ BSONObjBuilder cmd;
+ cmd.append( "checkShardingIndex" , ns );
+ cmd.append( "keyPattern" , key );
+ BSONObj cmdObj = cmd.obj();
+ if ( ! conn->runCommand( "admin" , cmdObj , res )) {
+ errmsg = res["errmsg"].str();
+ conn.done();
+ return false;
+ }
+ }
+
+ if ( ! hasShardIndex && ( conn->count( ns ) != 0 ) ) {
+ errmsg = "please create an index over the sharding key before sharding.";
+ result.append( "proposedKey" , key );
+ result.appendArray( "curIndexes" , allIndexes.done() );
+ conn.done();
+ return false;
+ }
+
+ conn.done();
+ }
+
+ tlog() << "CMD: shardcollection: " << cmdObj << endl;
+
+// vector<BSONObj> pts;
+// if (cmdObj.hasField("splitPoints")) {
+// if ( cmdObj.getField("splitPoints").type() != Array ) {
+// errmsg = "Value of splitPoints must be an array of objects";
+// return false;
+// }
+//
+// vector<BSONElement> elmts = cmdObj.getField("splitPoints").Array();
+// for ( unsigned i = 0 ; i < elmts.size() ; ++i) {
+// if ( elmts[i].type() != Object ) {
+// errmsg = "Elements in the splitPoints array must be objects";
+// return false;
+// }
+// pts.push_back( elmts[i].Obj() );
+// }
+// }
+ config->shardCollection( ns , key , careAboutUnique );
+
+ result << "collectionsharded" << ns;
+ return true;
+ }
+ } shardCollectionCmd;
+
+ class GetShardVersion : public GridAdminCmd {
+ public:
+ GetShardVersion() : GridAdminCmd( "getShardVersion" ) {}
+ virtual void help( stringstream& help ) const {
+ help << " example: { getShardVersion : 'alleyinsider.foo' } ";
+ }
+
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ string ns = cmdObj.firstElement().valuestrsafe();
+ if ( ns.size() == 0 ) {
+ errmsg = "need to specify fully namespace";
+ return false;
+ }
+
+ DBConfigPtr config = grid.getDBConfig( ns );
+ if ( ! config->isSharded( ns ) ) {
+ errmsg = "ns not sharded.";
+ return false;
+ }
+
+ ChunkManagerPtr cm = config->getChunkManagerIfExists( ns );
+ if ( ! cm ) {
+ errmsg = "no chunk manager?";
+ return false;
+ }
+ cm->_printChunks();
+ result.appendTimestamp( "version" , cm->getVersion().toLong() );
+
+ return 1;
+ }
+ } getShardVersionCmd;
+
+ class SplitCollectionCmd : public GridAdminCmd {
+ public:
+ SplitCollectionCmd() : GridAdminCmd( "split" ) {}
+ virtual void help( stringstream& help ) const {
+ help
+ << " example: - split the shard that contains give key \n"
+ << " { split : 'alleyinsider.blog.posts' , find : { ts : 1 } }\n"
+ << " example: - split the shard that contains the key with this as the middle \n"
+ << " { split : 'alleyinsider.blog.posts' , middle : { ts : 1 } }\n"
+ << " NOTE: this does not move move the chunks, it merely creates a logical separation \n"
+ ;
+ }
+
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+
+ if ( ! okForConfigChanges( errmsg ) )
+ return false;
+
+ ShardConnection::sync();
+
+ string ns = cmdObj.firstElement().valuestrsafe();
+ if ( ns.size() == 0 ) {
+ errmsg = "no ns";
+ return false;
+ }
+
+ DBConfigPtr config = grid.getDBConfig( ns );
+ if ( ! config->isSharded( ns ) ) {
+ config->reload();
+ if ( ! config->isSharded( ns ) ) {
+ errmsg = "ns not sharded. have to shard before can split";
+ return false;
+ }
+ }
+
+ BSONObj find = cmdObj.getObjectField( "find" );
+ if ( find.isEmpty() ) {
+ find = cmdObj.getObjectField( "middle" );
+
+ if ( find.isEmpty() ) {
+ errmsg = "need to specify find or middle";
+ return false;
+ }
+ }
+
+ ChunkManagerPtr info = config->getChunkManager( ns );
+ ChunkPtr chunk = info->findChunk( find );
+ BSONObj middle = cmdObj.getObjectField( "middle" );
+
+ assert( chunk.get() );
+ log() << "splitting: " << ns << " shard: " << chunk << endl;
+
+ BSONObj res;
+ bool worked;
+ if ( middle.isEmpty() ) {
+ BSONObj ret = chunk->singleSplit( true /* force a split even if not enough data */ , res );
+ worked = !ret.isEmpty();
+ }
+ else {
+ // sanity check if the key provided is a valid split point
+ if ( ( middle == chunk->getMin() ) || ( middle == chunk->getMax() ) ) {
+ errmsg = "cannot split on initial or final chunk's key";
+ return false;
+ }
+
+ if (!fieldsMatch(middle, info->getShardKey().key())){
+ errmsg = "middle has different fields (or different order) than shard key";
+ return false;
+ }
+
+ vector<BSONObj> splitPoints;
+ splitPoints.push_back( middle );
+ worked = chunk->multiSplit( splitPoints , res );
+ }
+
+ if ( !worked ) {
+ errmsg = "split failed";
+ result.append( "cause" , res );
+ return false;
+ }
+ config->getChunkManager( ns , true );
+ return true;
+ }
+ } splitCollectionCmd;
+
+ class MoveChunkCmd : public GridAdminCmd {
+ public:
+ MoveChunkCmd() : GridAdminCmd( "moveChunk" ) {}
+ virtual void help( stringstream& help ) const {
+ help << "{ movechunk : 'test.foo' , find : { num : 1 } , to : 'localhost:30001' }";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+
+ if ( ! okForConfigChanges( errmsg ) )
+ return false;
+
+ ShardConnection::sync();
+
+ Timer t;
+ string ns = cmdObj.firstElement().valuestrsafe();
+ if ( ns.size() == 0 ) {
+ errmsg = "no ns";
+ return false;
+ }
+
+ DBConfigPtr config = grid.getDBConfig( ns );
+ if ( ! config->isSharded( ns ) ) {
+ config->reload();
+ if ( ! config->isSharded( ns ) ) {
+ errmsg = "ns not sharded. have to shard before we can move a chunk";
+ return false;
+ }
+ }
+
+ BSONObj find = cmdObj.getObjectField( "find" );
+ if ( find.isEmpty() ) {
+ errmsg = "need to specify find. see help";
+ return false;
+ }
+
+ string toString = cmdObj["to"].valuestrsafe();
+ if ( ! toString.size() ) {
+ errmsg = "you have to specify where you want to move the chunk";
+ return false;
+ }
+
+ Shard to = Shard::make( toString );
+
+ // so far, chunk size serves test purposes; it may or may not become a supported parameter
+ long long maxChunkSizeBytes = cmdObj["maxChunkSizeBytes"].numberLong();
+ if ( maxChunkSizeBytes == 0 ) {
+ maxChunkSizeBytes = Chunk::MaxChunkSize;
+ }
+
+ tlog() << "CMD: movechunk: " << cmdObj << endl;
+
+ ChunkManagerPtr info = config->getChunkManager( ns );
+ ChunkPtr c = info->findChunk( find );
+ const Shard& from = c->getShard();
+
+ if ( from == to ) {
+ errmsg = "that chunk is already on that shard";
+ return false;
+ }
+
+ BSONObj res;
+ if ( ! c->moveAndCommit( to , maxChunkSizeBytes , res ) ) {
+ errmsg = "move failed";
+ result.append( "cause" , res );
+ return false;
+ }
+
+ // preemptively reload the config to get new version info
+ config->getChunkManager( ns , true );
+
+ result.append( "millis" , t.millis() );
+ return true;
+ }
+ } moveChunkCmd;
+
+ // ------------ server level commands -------------
+
+ class ListShardsCmd : public GridAdminCmd {
+ public:
+ ListShardsCmd() : GridAdminCmd("listShards") { }
+ virtual void help( stringstream& help ) const {
+ help << "list all shards of the system";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ ScopedDbConnection conn( configServer.getPrimary() );
+
+ vector<BSONObj> all;
+ auto_ptr<DBClientCursor> cursor = conn->query( "config.shards" , BSONObj() );
+ while ( cursor->more() ) {
+ BSONObj o = cursor->next();
+ all.push_back( o );
+ }
+
+ result.append("shards" , all );
+ conn.done();
+
+ return true;
+ }
+ } listShardsCmd;
+
+ /* a shard is a single mongod server or a replica pair. add it (them) to the cluster as a storage partition. */
+ class AddShard : public GridAdminCmd {
+ public:
+ AddShard() : GridAdminCmd("addShard") { }
+ virtual void help( stringstream& help ) const {
+ help << "add a new shard to the system";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ errmsg.clear();
+
+ // get replica set component hosts
+ ConnectionString servers = ConnectionString::parse( cmdObj.firstElement().valuestrsafe() , errmsg );
+ if ( ! errmsg.empty() ) {
+ log() << "addshard request " << cmdObj << " failed:" << errmsg << endl;
+ return false;
+ }
+
+ // using localhost in server names implies every other process must use localhost addresses too
+ vector<HostAndPort> serverAddrs = servers.getServers();
+ for ( size_t i = 0 ; i < serverAddrs.size() ; i++ ) {
+ if ( serverAddrs[i].isLocalHost() != grid.allowLocalHost() ) {
+ errmsg = str::stream() <<
+ "can't use localhost as a shard since all shards need to communicate. " <<
+ "either use all shards and configdbs in localhost or all in actual IPs " <<
+ " host: " << serverAddrs[i].toString() << " isLocalHost:" << serverAddrs[i].isLocalHost();
+
+ log() << "addshard request " << cmdObj << " failed: attempt to mix localhosts and IPs" << endl;
+ return false;
+ }
+
+ // it's fine if mongods of a set all use default port
+ if ( ! serverAddrs[i].hasPort() ) {
+ serverAddrs[i].setPort( CmdLine::ShardServerPort );
+ }
+ }
+
+ // name is optional; addShard will provide one if needed
+ string name = "";
+ if ( cmdObj["name"].type() == String ) {
+ name = cmdObj["name"].valuestrsafe();
+ }
+
+ // maxSize is the space usage cap in a shard in MBs
+ long long maxSize = 0;
+ if ( cmdObj[ ShardFields::maxSize.name() ].isNumber() ) {
+ maxSize = cmdObj[ ShardFields::maxSize.name() ].numberLong();
+ }
+
+ if ( ! grid.addShard( &name , servers , maxSize , errmsg ) ) {
+ log() << "addshard request " << cmdObj << " failed: " << errmsg << endl;
+ return false;
+ }
+
+ result << "shardAdded" << name;
+ return true;
+ }
+
+ } addServer;
+
+ /* See usage docs at:
+ * http://www.mongodb.org/display/DOCS/Configuring+Sharding#ConfiguringSharding-Removingashard
+ */
+ class RemoveShardCmd : public GridAdminCmd {
+ public:
+ RemoveShardCmd() : GridAdminCmd("removeShard") { }
+ virtual void help( stringstream& help ) const {
+ help << "remove a shard to the system.";
+ }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ string target = cmdObj.firstElement().valuestrsafe();
+ Shard s = Shard::make( target );
+ if ( ! grid.knowAboutShard( s.getConnString() ) ) {
+ errmsg = "unknown shard";
+ return false;
+ }
+
+ ScopedDbConnection conn( configServer.getPrimary() );
+
+ if (conn->count("config.shards", BSON("_id" << NE << s.getName() << ShardFields::draining(true)))){
+ conn.done();
+ errmsg = "Can't have more than one draining shard at a time";
+ return false;
+ }
+
+ if (conn->count("config.shards", BSON("_id" << NE << s.getName())) == 0){
+ conn.done();
+ errmsg = "Can't remove last shard";
+ return false;
+ }
+
+ BSONObj primaryDoc = BSON( "_id" << NE << "local" << "primary" << s.getName() );
+ BSONObj dbInfo; // appended at end of result on success
+ {
+ boost::scoped_ptr<DBClientCursor> cursor (conn->query("config.databases", primaryDoc));
+ if (cursor->more()) { // skip block and allocations if empty
+ BSONObjBuilder dbInfoBuilder;
+ dbInfoBuilder.append("note", "you need to drop or movePrimary these databases");
+ BSONArrayBuilder dbs(dbInfoBuilder.subarrayStart("dbsToMove"));
+
+ while (cursor->more()){
+ BSONObj db = cursor->nextSafe();
+ dbs.append(db["_id"]);
+ }
+ dbs.doneFast();
+
+ dbInfo = dbInfoBuilder.obj();
+ }
+ }
+
+ // If the server is not yet draining chunks, put it in draining mode.
+ BSONObj searchDoc = BSON( "_id" << s.getName() );
+ BSONObj drainingDoc = BSON( "_id" << s.getName() << ShardFields::draining(true) );
+ BSONObj shardDoc = conn->findOne( "config.shards", drainingDoc );
+ if ( shardDoc.isEmpty() ) {
+
+ // TODO prevent move chunks to this shard.
+
+ log() << "going to start draining shard: " << s.getName() << endl;
+ BSONObj newStatus = BSON( "$set" << BSON( ShardFields::draining(true) ) );
+ conn->update( "config.shards" , searchDoc , newStatus, false /* do no upsert */);
+
+ errmsg = conn->getLastError();
+ if ( errmsg.size() ) {
+ log() << "error starting remove shard: " << s.getName() << " err: " << errmsg << endl;
+ return false;
+ }
+
+ BSONObj primaryLocalDoc = BSON("_id" << "local" << "primary" << s.getName() );
+ PRINT(primaryLocalDoc);
+ if (conn->count("config.databases", primaryLocalDoc)) {
+ log() << "This shard is listed as primary of local db. Removing entry." << endl;
+ conn->remove("config.databases", BSON("_id" << "local"));
+ errmsg = conn->getLastError();
+ if ( errmsg.size() ) {
+ log() << "error removing local db: " << errmsg << endl;
+ return false;
+ }
+ }
+
+ Shard::reloadShardInfo();
+
+ result.append( "msg" , "draining started successfully" );
+ result.append( "state" , "started" );
+ result.append( "shard" , s.getName() );
+ result.appendElements(dbInfo);
+ conn.done();
+ return true;
+ }
+
+ // If the server has been completely drained, remove it from the ConfigDB.
+ // Check not only for chunks but also databases.
+ BSONObj shardIDDoc = BSON( "shard" << shardDoc[ "_id" ].str() );
+ long long chunkCount = conn->count( "config.chunks" , shardIDDoc );
+ long long dbCount = conn->count( "config.databases" , primaryDoc );
+ if ( ( chunkCount == 0 ) && ( dbCount == 0 ) ) {
+ log() << "going to remove shard: " << s.getName() << endl;
+ conn->remove( "config.shards" , searchDoc );
+
+ errmsg = conn->getLastError();
+ if ( errmsg.size() ) {
+ log() << "error concluding remove shard: " << s.getName() << " err: " << errmsg << endl;
+ return false;
+ }
+
+ Shard::removeShard( shardDoc[ "_id" ].str() );
+ Shard::reloadShardInfo();
+
+ result.append( "msg" , "removeshard completed successfully" );
+ result.append( "state" , "completed" );
+ result.append( "shard" , s.getName() );
+ conn.done();
+ return true;
+ }
+
+ // If the server is already in draining mode, just report on its progress.
+ // Report on databases (not just chunks) that are left too.
+ result.append( "msg" , "draining ongoing" );
+ result.append( "state" , "ongoing" );
+ BSONObjBuilder inner;
+ inner.append( "chunks" , chunkCount );
+ inner.append( "dbs" , dbCount );
+ result.append( "remaining" , inner.obj() );
+ result.appendElements(dbInfo);
+
+ conn.done();
+ return true;
+ }
+ } removeShardCmd;
+
+
+ // --------------- public commands ----------------
+
+ class IsDbGridCmd : public Command {
+ public:
+ virtual LockType locktype() const { return NONE; }
+ virtual bool requiresAuth() { return false; }
+ virtual bool slaveOk() const {
+ return true;
+ }
+ IsDbGridCmd() : Command("isdbgrid") { }
+ bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ result.append("isdbgrid", 1);
+ result.append("hostname", getHostNameCached());
+ return true;
+ }
+ } isdbgrid;
+
+ class CmdIsMaster : public Command {
+ public:
+ virtual LockType locktype() const { return NONE; }
+ virtual bool requiresAuth() { return false; }
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual void help( stringstream& help ) const {
+ help << "test if this is master half of a replica pair";
+ }
+ CmdIsMaster() : Command("isMaster" , false , "ismaster") { }
+ virtual bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ result.appendBool("ismaster", true );
+ result.append("msg", "isdbgrid");
+ result.appendNumber("maxBsonObjectSize", BSONObjMaxUserSize);
+ return true;
+ }
+ } ismaster;
+
+ class CmdWhatsMyUri : public Command {
+ public:
+ CmdWhatsMyUri() : Command("whatsmyuri") { }
+ virtual bool logTheOp() {
+ return false; // the modification will be logged directly
+ }
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual LockType locktype() const { return NONE; }
+ virtual void help( stringstream &help ) const {
+ help << "{whatsmyuri:1}";
+ }
+ virtual bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ result << "you" << ClientInfo::get()->getRemote();
+ return true;
+ }
+ } cmdWhatsMyUri;
+
+
+ class CmdShardingGetPrevError : public Command {
+ public:
+ virtual LockType locktype() const { return NONE; }
+ virtual bool requiresAuth() { return false; }
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual void help( stringstream& help ) const {
+ help << "get previous error (since last reseterror command)";
+ }
+ CmdShardingGetPrevError() : Command( "getPrevError" , false , "getpreverror") { }
+ virtual bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ errmsg += "getpreverror not supported for sharded environments";
+ return false;
+ }
+ } cmdGetPrevError;
+
+ class CmdShardingGetLastError : public Command {
+ public:
+ virtual LockType locktype() const { return NONE; }
+ virtual bool requiresAuth() { return false; }
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual void help( stringstream& help ) const {
+ help << "check for an error on the last command executed";
+ }
+ CmdShardingGetLastError() : Command("getLastError" , false , "getlasterror") { }
+
+ virtual bool run(const string& dbName, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool) {
+ LastError *le = lastError.disableForCommand();
+ {
+ assert( le );
+ if ( le->msg.size() && le->nPrev == 1 ) {
+ le->appendSelf( result );
+ return true;
+ }
+ }
+
+ ClientInfo * client = ClientInfo::get();
+ return client->getLastError( cmdObj , result );
+ }
+ } cmdGetLastError;
+
+ }
+
+ class CmdShardingResetError : public Command {
+ public:
+ CmdShardingResetError() : Command( "resetError" , false , "reseterror" ) {}
+
+ virtual LockType locktype() const { return NONE; }
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ bool run(const string& dbName , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) {
+ LastError *le = lastError.get();
+ if ( le )
+ le->reset();
+
+ ClientInfo * client = ClientInfo::get();
+ set<string> * shards = client->getPrev();
+
+ for ( set<string>::iterator i = shards->begin(); i != shards->end(); i++ ) {
+ string theShard = *i;
+ ShardConnection conn( theShard , "" );
+ BSONObj res;
+ conn->runCommand( dbName , cmdObj , res );
+ conn.done();
+ }
+
+ return true;
+ }
+ } cmdShardingResetError;
+
+ class CmdListDatabases : public Command {
+ public:
+ CmdListDatabases() : Command("listDatabases", true , "listdatabases" ) {}
+
+ virtual bool logTheOp() { return false; }
+ virtual bool slaveOk() const { return true; }
+ virtual bool slaveOverrideOk() { return true; }
+ virtual bool adminOnly() const { return true; }
+ virtual LockType locktype() const { return NONE; }
+ virtual void help( stringstream& help ) const { help << "list databases on cluster"; }
+
+ bool run(const string& , BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) {
+ vector<Shard> shards;
+ Shard::getAllShards( shards );
+
+ map<string,long long> sizes;
+ map< string,shared_ptr<BSONObjBuilder> > dbShardInfo;
+
+ for ( vector<Shard>::iterator i=shards.begin(); i!=shards.end(); i++ ) {
+ Shard s = *i;
+ BSONObj x = s.runCommand( "admin" , "listDatabases" );
+
+ BSONObjIterator j( x["databases"].Obj() );
+ while ( j.more() ) {
+ BSONObj theDB = j.next().Obj();
+
+ string name = theDB["name"].String();
+ long long size = theDB["sizeOnDisk"].numberLong();
+
+ long long& totalSize = sizes[name];
+ if ( size == 1 ) {
+ if ( totalSize <= 1 )
+ totalSize = 1;
+ }
+ else
+ totalSize += size;
+
+ shared_ptr<BSONObjBuilder>& bb = dbShardInfo[name];
+ if ( ! bb.get() )
+ bb.reset( new BSONObjBuilder() );
+ bb->appendNumber( s.getName() , size );
+ }
+
+ }
+
+ long long totalSize = 0;
+
+ BSONArrayBuilder bb( result.subarrayStart( "databases" ) );
+ for ( map<string,long long>::iterator i=sizes.begin(); i!=sizes.end(); ++i ) {
+ string name = i->first;
+
+ if ( name == "local" ) {
+ // we don't return local
+ // since all shards have their own independent local
+ continue;
+ }
+
+ long long size = i->second;
+ totalSize += size;
+
+ BSONObjBuilder temp;
+ temp.append( "name" , name );
+ temp.appendNumber( "sizeOnDisk" , size );
+ temp.appendBool( "empty" , size == 1 );
+ temp.append( "shards" , dbShardInfo[name]->obj() );
+
+ bb.append( temp.obj() );
+ }
+
+ if ( sizes.find( "config" ) == sizes.end() ){
+ ScopedDbConnection conn( configServer.getPrimary() );
+ BSONObj x;
+ if ( conn->simpleCommand( "config" , &x , "dbstats" ) ){
+ BSONObjBuilder b;
+ b.append( "name" , "config" );
+ b.appendBool( "empty" , false );
+ if ( x["fileSize"].type() )
+ b.appendAs( x["fileSize"] , "sizeOnDisk" );
+ else
+ b.append( "sizeOnDisk" , 1 );
+ bb.append( b.obj() );
+ }
+ else {
+ bb.append( BSON( "name" << "config" ) );
+ }
+ conn.done();
+ }
+
+ bb.done();
+
+ result.appendNumber( "totalSize" , totalSize );
+ result.appendNumber( "totalSizeMb" , totalSize / ( 1024 * 1024 ) );
+
+ return 1;
+ }
+
+ } cmdListDatabases;
+
+ class CmdCloseAllDatabases : public Command {
+ public:
+ CmdCloseAllDatabases() : Command("closeAllDatabases", false , "closeAllDatabases" ) {}
+ virtual bool logTheOp() { return false; }
+ virtual bool slaveOk() const { return true; }
+ virtual bool slaveOverrideOk() { return true; }
+ virtual bool adminOnly() const { return true; }
+ virtual LockType locktype() const { return NONE; }
+ virtual void help( stringstream& help ) const { help << "Not supported sharded"; }
+
+ bool run(const string& , BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& /*result*/, bool /*fromRepl*/) {
+ errmsg = "closeAllDatabases isn't supported through mongos";
+ return false;
+ }
+ } cmdCloseAllDatabases;
+
+
+ class CmdReplSetGetStatus : public Command {
+ public:
+ CmdReplSetGetStatus() : Command("replSetGetStatus"){}
+ virtual bool logTheOp() { return false; }
+ virtual bool slaveOk() const { return true; }
+ virtual bool adminOnly() const { return true; }
+ virtual LockType locktype() const { return NONE; }
+ virtual void help( stringstream& help ) const { help << "Not supported through mongos"; }
+
+ bool run(const string& , BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) {
+ if ( jsobj["forShell"].trueValue() )
+ lastError.disableForCommand();
+
+ errmsg = "replSetGetStatus is not supported through mongos";
+ result.append("info", "mongos"); // see sayReplSetMemberState
+ return false;
+ }
+ } cmdReplSetGetStatus;
+
+ CmdShutdown cmdShutdown;
+
+ void CmdShutdown::help( stringstream& help ) const {
+ help << "shutdown the database. must be ran against admin db and "
+ << "either (1) ran from localhost or (2) authenticated.";
+ }
+
+ bool CmdShutdown::run(const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
+ return shutdownHelper();
+ }
+
+} // namespace mongo