// s/commands_public.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 .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
#include "mongo/platform/basic.h"
#include
#include "mongo/client/connpool.h"
#include "mongo/client/parallel.h"
#include "mongo/db/auth/action_set.h"
#include "mongo/db/auth/action_type.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/authorization_manager_global.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/commands.h"
#include "mongo/db/commands/copydb.h"
#include "mongo/db/commands/find_and_modify.h"
#include "mongo/db/commands/rename_collection.h"
#include "mongo/db/lasterror.h"
#include "mongo/db/query/lite_parsed_query.h"
#include "mongo/s/catalog/catalog_cache.h"
#include "mongo/s/catalog/catalog_manager.h"
#include "mongo/s/chunk_manager.h"
#include "mongo/s/client/shard_connection.h"
#include "mongo/s/client/shard_registry.h"
#include "mongo/s/cluster_explain.h"
#include "mongo/s/cluster_last_error_info.h"
#include "mongo/s/commands/cluster_commands_common.h"
#include "mongo/s/commands/run_on_all_shards_cmd.h"
#include "mongo/s/config.h"
#include "mongo/s/grid.h"
#include "mongo/s/stale_exception.h"
#include "mongo/s/version_manager.h"
#include "mongo/scripting/engine.h"
#include "mongo/util/log.h"
#include "mongo/util/net/message.h"
#include "mongo/util/timer.h"
namespace mongo {
using boost::intrusive_ptr;
using std::unique_ptr;
using boost::shared_ptr;
using std::list;
using std::make_pair;
using std::map;
using std::multimap;
using std::set;
using std::string;
using std::stringstream;
using std::vector;
namespace dbgrid_pub_cmds {
class PublicGridCommand : public Command {
public:
PublicGridCommand( const char* n, const char* oldname=NULL ) : Command( n, false, oldname ) {
}
virtual bool slaveOk() const {
return true;
}
virtual bool adminOnly() const {
return false;
}
// Override if passthrough should also send query options
// Safer as off by default, can slowly enable as we add more tests
virtual bool passOptions() const { return false; }
// all grid commands are designed not to lock
virtual bool isWriteCommandForConfigServer() const { return false; }
protected:
bool passthrough( DBConfigPtr conf, const BSONObj& cmdObj , BSONObjBuilder& result ) {
return _passthrough(conf->name(), conf, cmdObj, 0, result);
}
bool adminPassthrough( DBConfigPtr conf, const BSONObj& cmdObj , BSONObjBuilder& result ) {
return _passthrough("admin", conf, cmdObj, 0, result);
}
bool passthrough( DBConfigPtr conf, const BSONObj& cmdObj , int options, BSONObjBuilder& result ) {
return _passthrough(conf->name(), conf, cmdObj, options, result);
}
private:
bool _passthrough(const string& db,
DBConfigPtr conf,
const BSONObj& cmdObj,
int options, BSONObjBuilder& result) {
const auto& shard = grid.shardRegistry()->findIfExists(conf->getPrimaryId());
ShardConnection conn(shard->getConnString(), "");
BSONObj res;
bool ok = conn->runCommand(db, cmdObj, res, passOptions() ? options : 0);
conn.done();
result.appendElements(res);
return ok;
}
};
class AllShardsCollectionCommand : public RunOnAllShardsCommand {
public:
AllShardsCollectionCommand(const char* n,
const char* oldname = NULL,
bool useShardConn = false):
RunOnAllShardsCommand(n, oldname, useShardConn) {
}
virtual void getShardIds(const string& dbName,
BSONObj& cmdObj,
vector& shardIds) {
const string fullns = dbName + '.' + cmdObj.firstElement().valuestrsafe();
auto status = grid.catalogCache()->getDatabase(dbName);
uassertStatusOK(status.getStatus());
shared_ptr conf = status.getValue();
if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
shardIds.push_back(conf->getShardId(fullns));
}
else {
grid.shardRegistry()->getAllShardIds(&shardIds);
}
}
};
class NotAllowedOnShardedCollectionCmd : public PublicGridCommand {
public:
NotAllowedOnShardedCollectionCmd( const char * n ) : PublicGridCommand( n ) {}
virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
const string fullns = parseNs(dbName, cmdObj);
auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
if (!conf->isSharded(fullns)) {
return passthrough( conf , cmdObj , options, result );
}
return appendCommandStatus(result,
Status(ErrorCodes::IllegalOperation,
str::stream() << "can't do command: " << name
<< " on sharded collection"));
}
};
// ----
class DropIndexesCmd : public AllShardsCollectionCommand {
public:
DropIndexesCmd() : AllShardsCollectionCommand("dropIndexes", "deleteIndexes") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::dropIndex);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
} dropIndexesCmd;
class CreateIndexesCmd : public AllShardsCollectionCommand {
public:
CreateIndexesCmd():
AllShardsCollectionCommand("createIndexes",
NULL, /* oldName */
true /* use ShardConnection */) {
// createIndexes command should use ShardConnection so the getLastError would
// be able to properly enforce the write concern (via the saveGLEStats callback).
}
/**
* the createIndexes command doesn't require the 'ns' field to be populated
* so we make sure its here as its needed for the system.indexes insert
*/
BSONObj fixSpec( const NamespaceString& ns, const BSONObj& original ) const {
if ( original["ns"].type() == String )
return original;
BSONObjBuilder bb;
bb.appendElements( original );
bb.append( "ns", ns.toString() );
return bb.obj();
}
/**
* @return equivalent of gle
*/
BSONObj createIndexLegacy( const string& server,
const NamespaceString& nss,
const BSONObj& spec ) const {
try {
ScopedDbConnection conn( server );
conn->insert( nss.getSystemIndexesCollection(), spec );
BSONObj gle = conn->getLastErrorDetailed( nss.db().toString() );
conn.done();
return gle;
}
catch ( DBException& e ) {
BSONObjBuilder b;
b.append( "errmsg", e.toString() );
b.append( "code", e.getCode() );
return b.obj();
}
}
virtual BSONObj specialErrorHandler( const string& server,
const string& dbName,
const BSONObj& cmdObj,
const BSONObj& originalResult ) const {
string errmsg = originalResult["errmsg"];
if ( errmsg.find( "no such cmd" ) == string::npos ) {
// cannot use codes as 2.4 didn't have a code for this
return originalResult;
}
// we need to down convert
NamespaceString nss( dbName, cmdObj["createIndexes"].String() );
if ( cmdObj["indexes"].type() != Array )
return originalResult;
BSONObjBuilder newResult;
newResult.append( "note", "downgraded" );
newResult.append( "sentTo", server );
BSONArrayBuilder individualResults;
bool ok = true;
BSONObjIterator indexIterator( cmdObj["indexes"].Obj() );
while ( indexIterator.more() ) {
BSONObj spec = indexIterator.next().Obj();
spec = fixSpec( nss, spec );
BSONObj gle = createIndexLegacy( server, nss, spec );
individualResults.append( BSON( "spec" << spec <<
"gle" << gle ) );
BSONElement e = gle["errmsg"];
if ( e.type() == String && e.String().size() > 0 ) {
ok = false;
newResult.appendAs( e, "errmsg" );
break;
}
e = gle["err"];
if ( e.type() == String && e.String().size() > 0 ) {
ok = false;
newResult.appendAs( e, "errmsg" );
break;
}
}
newResult.append( "eachIndex", individualResults.arr() );
newResult.append( "ok", ok ? 1 : 0 );
return newResult.obj();
}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::createIndex);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
} createIndexesCmd;
class ReIndexCmd : public AllShardsCollectionCommand {
public:
ReIndexCmd() : AllShardsCollectionCommand("reIndex") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::reIndex);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
} reIndexCmd;
class CollectionModCmd : public AllShardsCollectionCommand {
public:
CollectionModCmd() : AllShardsCollectionCommand("collMod") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::collMod);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
} collectionModCmd;
class ValidateCmd : public AllShardsCollectionCommand {
public:
ValidateCmd() : AllShardsCollectionCommand("validate") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::validate);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
virtual void aggregateResults(const vector& results,
BSONObjBuilder& output) {
for (vector::const_iterator it(results.begin()), end(results.end());
it!=end; it++) {
const BSONObj& result = std::get<1>(*it);
const BSONElement valid = result["valid"];
if (!valid.eoo()){
if (!valid.trueValue()) {
output.appendBool("valid", false);
return;
}
}
else {
// Support pre-1.9.0 output with everything in a big string
const char* s = result["result"].valuestrsafe();
if (strstr(s, "exception") || strstr(s, "corrupt")){
output.appendBool("valid", false);
return;
}
}
}
output.appendBool("valid", true);
}
} validateCmd;
class RepairDatabaseCmd : public RunOnAllShardsCommand {
public:
RepairDatabaseCmd() : RunOnAllShardsCommand("repairDatabase") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::repairDatabase);
out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
}
} repairDatabaseCmd;
class DBStatsCmd : public RunOnAllShardsCommand {
public:
DBStatsCmd() : RunOnAllShardsCommand("dbStats", "dbstats") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::dbStats);
out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
}
virtual void aggregateResults(const vector& results,
BSONObjBuilder& output) {
long long objects = 0;
long long unscaledDataSize = 0;
long long dataSize = 0;
long long storageSize = 0;
long long numExtents = 0;
long long indexes = 0;
long long indexSize = 0;
long long fileSize = 0;
long long freeListNum = 0;
long long freeListSize = 0;
for (vector::const_iterator it(results.begin()), end(results.end());
it != end; ++it) {
const BSONObj& b = std::get<1>(*it);
objects += b["objects"].numberLong();
unscaledDataSize += b["avgObjSize"].numberLong() * b["objects"].numberLong();
dataSize += b["dataSize"].numberLong();
storageSize += b["storageSize"].numberLong();
numExtents += b["numExtents"].numberLong();
indexes += b["indexes"].numberLong();
indexSize += b["indexSize"].numberLong();
fileSize += b["fileSize"].numberLong();
if ( b["extentFreeList"].isABSONObj() ) {
freeListNum += b["extentFreeList"].Obj()["num"].numberLong();
freeListSize += b["extentFreeList"].Obj()["totalSize"].numberLong();
}
}
//result.appendNumber( "collections" , ncollections ); //TODO: need to find a good way to get this
output.appendNumber( "objects" , objects );
/* avgObjSize on mongod is not scaled based on the argument to db.stats(), so we use
* unscaledDataSize here for consistency. See SERVER-7347. */
output.append ( "avgObjSize" , objects == 0 ? 0 : double(unscaledDataSize) /
double(objects) );
output.appendNumber( "dataSize" , dataSize );
output.appendNumber( "storageSize" , storageSize);
output.appendNumber( "numExtents" , numExtents );
output.appendNumber( "indexes" , indexes );
output.appendNumber( "indexSize" , indexSize );
output.appendNumber( "fileSize" , fileSize );
{
BSONObjBuilder extentFreeList( output.subobjStart( "extentFreeList" ) );
extentFreeList.appendNumber( "num", freeListNum );
extentFreeList.appendNumber( "totalSize", freeListSize );
extentFreeList.done();
}
}
} DBStatsCmdObj;
class CreateCmd : public PublicGridCommand {
public:
CreateCmd() : PublicGridCommand( "create" ) {}
virtual Status checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
AuthorizationSession* authzSession = AuthorizationSession::get(client);
if (cmdObj["capped"].trueValue()) {
if (!authzSession->isAuthorizedForActionsOnResource(
parseResourcePattern(dbname, cmdObj), ActionType::convertToCapped)) {
return Status(ErrorCodes::Unauthorized, "unauthorized");
}
}
// ActionType::createCollection or ActionType::insert are both acceptable
if (authzSession->isAuthorizedForActionsOnResource(
parseResourcePattern(dbname, cmdObj), ActionType::createCollection) ||
authzSession->isAuthorizedForActionsOnResource(
parseResourcePattern(dbname, cmdObj), ActionType::insert)) {
return Status::OK();
}
return Status(ErrorCodes::Unauthorized, "unauthorized");
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
auto status = grid.implicitCreateDb(dbName);
if (!status.isOK()) {
return appendCommandStatus(result, status.getStatus());
}
shared_ptr conf = status.getValue();
return passthrough(conf, cmdObj, result);
}
} createCmd;
class DropCmd : public PublicGridCommand {
public:
DropCmd() : PublicGridCommand( "drop" ) {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::dropCollection);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
auto status = grid.catalogCache()->getDatabase(dbName);
if (!status.isOK()) {
if (status == ErrorCodes::DatabaseNotFound) {
return true;
}
return appendCommandStatus(result, status.getStatus());
}
shared_ptr conf = status.getValue();
const string fullns = dbName + "." + cmdObj.firstElement().valuestrsafe();
log() << "DROP: " << fullns;
if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
log() << "\tdrop going to do passthrough";
return passthrough( conf , cmdObj , result );
}
//
// TODO: There will be problems if we simultaneously shard and drop a collection
//
ChunkManagerPtr cm;
ShardPtr primary;
conf->getChunkManagerOrPrimary( fullns, cm, primary );
if( ! cm ) {
log() << "\tdrop going to do passthrough after re-check";
return passthrough( conf , cmdObj , result );
}
uassertStatusOK(grid.catalogManager()->dropCollection(fullns));
if( ! conf->removeSharding( fullns ) ){
warning() << "collection " << fullns
<< " was reloaded as unsharded before drop completed"
<< " during single drop";
}
return 1;
}
} dropCmd;
class RenameCollectionCmd : public PublicGridCommand {
public:
RenameCollectionCmd() : PublicGridCommand( "renameCollection" ) {}
virtual Status checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
return rename_collection::checkAuthForRenameCollectionCommand(client,
dbname,
cmdObj);
}
virtual bool adminOnly() const {
return true;
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
const string fullnsFrom = cmdObj.firstElement().valuestrsafe();
const string dbNameFrom = nsToDatabase(fullnsFrom);
auto confFrom = uassertStatusOK(grid.catalogCache()->getDatabase(dbNameFrom));
const string fullnsTo = cmdObj["to"].valuestrsafe();
const string dbNameTo = nsToDatabase(fullnsTo);
auto confTo = uassertStatusOK(grid.catalogCache()->getDatabase(dbNameTo));
uassert(13138, "You can't rename a sharded collection", !confFrom->isSharded(fullnsFrom));
uassert(13139, "You can't rename to a sharded collection", !confTo->isSharded(fullnsTo));
const ShardId& shardTo = confTo->getShardId(fullnsTo);
const ShardId& shardFrom = confFrom->getShardId(fullnsFrom);
uassert(13137,
"Source and destination collections must be on same shard",
shardFrom == shardTo);
return adminPassthrough( confFrom , cmdObj , result );
}
} renameCollectionCmd;
class CopyDBCmd : public PublicGridCommand {
public:
CopyDBCmd() : PublicGridCommand( "copydb" ) {}
virtual bool adminOnly() const {
return true;
}
virtual Status checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
return copydb::checkAuthForCopydbCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
const string todb = cmdObj.getStringField("todb");
uassert(ErrorCodes::EmptyFieldName, "missing todb argument", !todb.empty());
uassert(ErrorCodes::InvalidNamespace, "invalid todb argument", nsIsDbOnly(todb));
auto confTo = uassertStatusOK(grid.implicitCreateDb(todb));
uassert(ErrorCodes::IllegalOperation,
"cannot copy to a sharded database",
!confTo->isShardingEnabled());
const string fromhost = cmdObj.getStringField("fromhost");
if (!fromhost.empty()) {
return adminPassthrough( confTo , cmdObj , result );
}
else {
const string fromdb = cmdObj.getStringField("fromdb");
uassert(13399, "need a fromdb argument", !fromdb.empty());
shared_ptr confFrom =
uassertStatusOK(grid.catalogCache()->getDatabase(fromdb));
uassert(13400, "don't know where source DB is", confFrom);
uassert(13401, "cant copy from sharded DB", !confFrom->isShardingEnabled());
BSONObjBuilder b;
BSONForEach(e, cmdObj) {
if (strcmp(e.fieldName(), "fromhost") != 0) {
b.append(e);
}
}
{
const auto& shard =
grid.shardRegistry()->findIfExists(confFrom->getPrimaryId());
b.append("fromhost", shard->getConnString().toString());
}
BSONObj fixed = b.obj();
return adminPassthrough( confTo , fixed , result );
}
}
} clusterCopyDBCmd;
class CollectionStats : public PublicGridCommand {
public:
CollectionStats() : PublicGridCommand("collStats", "collstats") { }
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::collStats);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
const string fullns = parseNs(dbName, cmdObj);
auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
result.appendBool("sharded", false);
result.append("primary", conf->getPrimaryId());
return passthrough( conf , cmdObj , result);
}
result.appendBool("sharded", true);
ChunkManagerPtr cm = conf->getChunkManager( fullns );
massert( 12594 , "how could chunk manager be null!" , cm );
BSONObjBuilder shardStats;
map counts;
map indexSizes;
/*
long long count=0;
long long size=0;
long long storageSize=0;
*/
int nindexes=0;
bool warnedAboutIndexes = false;
set shardIds;
cm->getAllShardIds(&shardIds);
for (const ShardId& shardId : shardIds) {
const auto& shard = grid.shardRegistry()->findIfExists(shardId);
if (!shard) {
continue;
}
BSONObj res;
{
ScopedDbConnection conn(shard->getConnString());
if ( ! conn->runCommand( dbName , cmdObj , res ) ) {
if ( !res["code"].eoo() ) {
result.append( res["code"] );
}
errmsg = "failed on shard: " + res.toString();
return false;
}
conn.done();
}
BSONObjIterator j( res );
while ( j.more() ) {
BSONElement e = j.next();
if ( str::equals( e.fieldName() , "ns" ) ||
str::equals( e.fieldName() , "ok" ) ||
str::equals( e.fieldName() , "avgObjSize" ) ||
str::equals( e.fieldName() , "lastExtentSize" ) ||
str::equals( e.fieldName() , "paddingFactor" ) ) {
continue;
}
else if ( str::equals( e.fieldName() , "count" ) ||
str::equals( e.fieldName() , "size" ) ||
str::equals( e.fieldName() , "storageSize" ) ||
str::equals( e.fieldName() , "numExtents" ) ||
str::equals( e.fieldName() , "totalIndexSize" ) ) {
counts[e.fieldName()] += e.numberLong();
}
else if ( str::equals( e.fieldName() , "indexSizes" ) ) {
BSONObjIterator k( e.Obj() );
while ( k.more() ) {
BSONElement temp = k.next();
indexSizes[temp.fieldName()] += temp.numberLong();
}
}
// no longer used since 2.2
else if ( str::equals( e.fieldName() , "flags" ) ) {
if ( ! result.hasField( e.fieldName() ) )
result.append( e );
}
// flags broken out in 2.4+
else if ( str::equals( e.fieldName() , "systemFlags" ) ) {
if ( ! result.hasField( e.fieldName() ) )
result.append( e );
}
else if ( str::equals( e.fieldName() , "userFlags" ) ) {
if ( ! result.hasField( e.fieldName() ) )
result.append( e );
}
else if ( str::equals( e.fieldName() , "capped" ) ) {
if ( ! result.hasField( e.fieldName() ) )
result.append( e );
}
else if ( str::equals( e.fieldName() , "paddingFactorNote" ) ) {
if ( ! result.hasField( e.fieldName() ) )
result.append( e );
}
else if ( str::equals( e.fieldName() , "indexDetails" ) ) {
//skip this field in the rollup
}
else if ( str::equals( e.fieldName() , "wiredTiger" ) ) {
//skip this field in the rollup
}
else if ( str::equals( e.fieldName() , "nindexes" ) ) {
int myIndexes = e.numberInt();
if ( nindexes == 0 ) {
nindexes = myIndexes;
}
else if ( nindexes == myIndexes ) {
// no-op
}
else {
// hopefully this means we're building an index
if ( myIndexes > nindexes )
nindexes = myIndexes;
if ( ! warnedAboutIndexes ) {
result.append( "warning" , "indexes don't all match - ok if ensureIndex is running" );
warnedAboutIndexes = true;
}
}
}
else {
warning() << "mongos collstats doesn't know about: " << e.fieldName();
}
}
shardStats.append(shardId, res);
}
result.append("ns", fullns);
for ( map::iterator i=counts.begin(); i!=counts.end(); ++i )
result.appendNumber( i->first , i->second );
{
BSONObjBuilder ib( result.subobjStart( "indexSizes" ) );
for ( map::iterator i=indexSizes.begin(); i!=indexSizes.end(); ++i )
ib.appendNumber( i->first , i->second );
ib.done();
}
if ( counts["count"] > 0 )
result.append("avgObjSize", (double)counts["size"] / (double)counts["count"] );
else
result.append( "avgObjSize", 0.0 );
result.append("nindexes", nindexes);
result.append("nchunks", cm->numChunks());
result.append("shards", shardStats.obj());
return true;
}
} collectionStatsCmd;
class FindAndModifyCmd : public PublicGridCommand {
public:
FindAndModifyCmd() : PublicGridCommand("findAndModify", "findandmodify") { }
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
find_and_modify::addPrivilegesRequiredForFindAndModify(this, dbname, cmdObj, out);
}
Status explain(OperationContext* txn,
const std::string& dbName,
const BSONObj& cmdObj,
ExplainCommon::Verbosity verbosity,
BSONObjBuilder* out) const {
const string ns = parseNsCollectionRequired(dbName, cmdObj);
auto status = grid.catalogCache()->getDatabase(dbName);
uassertStatusOK(status);
DBConfigPtr conf = status.getValue();
ShardPtr shard;
if (!conf->isShardingEnabled() || !conf->isSharded(ns)) {
shard = grid.shardRegistry()->findIfExists(conf->getPrimaryId());
}
else {
ChunkManagerPtr chunkMgr = getChunkManager(conf, ns);
const BSONObj query = cmdObj.getObjectField("query");
StatusWith status = getShardKey(chunkMgr, ns, query);
if (!status.isOK()) {
return status.getStatus();
}
BSONObj shardKey = status.getValue();
ChunkPtr chunk = chunkMgr->findIntersectingChunk(shardKey);
shard = grid.shardRegistry()->findIfExists(chunk->getShardId());
}
BSONObjBuilder explainCmd;
ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmd);
// Time how long it takes to run the explain command on the shard.
Timer timer;
BSONObjBuilder result;
bool ok = runCommand(conf, shard->getId(), ns, explainCmd.obj(), result);
long long millisElapsed = timer.millis();
if (!ok) {
BSONObj res = result.obj();
return Status(ErrorCodes::OperationFailed, str::stream()
<< "Explain for findAndModify command failed: " << res);
}
Strategy::CommandResult cmdResult;
cmdResult.shardTargetId = shard->getId();
cmdResult.target = shard->getConnString();
cmdResult.result = result.obj();
vector shardResults;
shardResults.push_back(cmdResult);
return ClusterExplain::buildExplainResult(shardResults,
ClusterExplain::kSingleShard,
millisElapsed,
out);
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
const string ns = parseNsCollectionRequired(dbName, cmdObj);
// findAndModify should only be creating database if upsert is true, but this
// would require that the parsing be pulled into this function.
auto conf = uassertStatusOK(grid.implicitCreateDb(dbName));
if (!conf->isShardingEnabled() || !conf->isSharded(ns)) {
return runCommand(conf, conf->getPrimaryId(), ns, cmdObj, result);
}
ChunkManagerPtr chunkMgr = getChunkManager(conf, ns);
const BSONObj query = cmdObj.getObjectField("query");
StatusWith status = getShardKey(chunkMgr, ns, query);
// Bad query
if (!status.isOK()) {
return appendCommandStatus(result, status.getStatus());
}
BSONObj shardKey = status.getValue();
ChunkPtr chunk = chunkMgr->findIntersectingChunk(shardKey);
bool ok = runCommand(conf, chunk->getShardId(), ns, cmdObj, result);
if (ok) {
// check whether split is necessary (using update object for size heuristic)
if (Chunk::ShouldAutoSplit) {
chunk->splitIfShould(cmdObj.getObjectField("update").objsize());
}
}
return ok;
}
private:
ChunkManagerPtr getChunkManager(DBConfigPtr conf, const string& ns) const {
ChunkManagerPtr chunkMgr = conf->getChunkManager(ns);
massert(13002, "shard internal error chunk manager should never be null", chunkMgr);
return chunkMgr;
}
StatusWith getShardKey(ChunkManagerPtr chunkMgr,
const string& ns,
const BSONObj& query) const {
// Verify that the query has an equality predicate using the shard key.
StatusWith status =
chunkMgr->getShardKeyPattern().extractShardKeyFromQuery(query);
if (status.isOK()) {
BSONObj shardKey = status.getValue();
uassert(13343, "query for sharded findAndModify must have shardkey",
!shardKey.isEmpty());
}
return status;
}
bool runCommand(DBConfigPtr conf,
const ShardId& shardId,
const string& ns,
const BSONObj& cmdObj,
BSONObjBuilder& result) const {
BSONObj res;
const auto& shard = grid.shardRegistry()->findIfExists(shardId);
ShardConnection conn(shard->getConnString(), ns);
bool ok = conn->runCommand(conf->name(), cmdObj, res);
conn.done();
// RecvStaleConfigCode is the code for RecvStaleConfigException.
if (!ok && res.getIntField("code") == RecvStaleConfigCode) {
// Command code traps this exception and re-runs
throw RecvStaleConfigException("FindAndModify", res);
}
result.appendElements(res);
return ok;
}
} findAndModifyCmd;
class DataSizeCmd : public PublicGridCommand {
public:
DataSizeCmd() : PublicGridCommand("dataSize", "datasize") { }
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::find);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
const string fullns = parseNs(dbName, cmdObj);
auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
return passthrough( conf , cmdObj , result);
}
ChunkManagerPtr cm = conf->getChunkManager( fullns );
massert( 13407 , "how could chunk manager be null!" , cm );
BSONObj min = cmdObj.getObjectField( "min" );
BSONObj max = cmdObj.getObjectField( "max" );
BSONObj keyPattern = cmdObj.getObjectField( "keyPattern" );
uassert( 13408, "keyPattern must equal shard key",
cm->getShardKeyPattern().toBSON() == keyPattern );
uassert( 13405, str::stream() << "min value " << min << " does not have shard key",
cm->getShardKeyPattern().isShardKey(min) );
uassert( 13406, str::stream() << "max value " << max << " does not have shard key",
cm->getShardKeyPattern().isShardKey(max) );
min = cm->getShardKeyPattern().normalizeShardKey(min);
max = cm->getShardKeyPattern().normalizeShardKey(max);
// yes these are doubles...
double size = 0;
double numObjects = 0;
int millis = 0;
set shardIds;
cm->getShardIdsForRange(shardIds, min, max);
for (const ShardId& shardId : shardIds) {
const auto& shard = grid.shardRegistry()->findIfExists(shardId);
if (!shard) {
continue;
}
ScopedDbConnection conn(shard->getConnString());
BSONObj res;
bool ok = conn->runCommand( conf->name() , cmdObj , res );
conn.done();
if ( ! ok ) {
result.appendElements( res );
return false;
}
size += res["size"].number();
numObjects += res["numObjects"].number();
millis += res["millis"].numberInt();
}
result.append( "size", size );
result.append( "numObjects" , numObjects );
result.append( "millis" , millis );
return true;
}
} DataSizeCmd;
class ConvertToCappedCmd : public NotAllowedOnShardedCollectionCmd {
public:
ConvertToCappedCmd() : NotAllowedOnShardedCollectionCmd("convertToCapped") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::convertToCapped);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
} convertToCappedCmd;
class GroupCmd : public NotAllowedOnShardedCollectionCmd {
public:
GroupCmd() : NotAllowedOnShardedCollectionCmd("group") {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::find);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
virtual bool passOptions() const { return true; }
virtual std::string parseNs(const std::string& dbName, const BSONObj& cmdObj) const {
return dbName + "." + cmdObj.firstElement()
.embeddedObjectUserCheck()["ns"]
.valuestrsafe();
}
Status explain(OperationContext* txn,
const std::string& dbname,
const BSONObj& cmdObj,
ExplainCommon::Verbosity verbosity,
BSONObjBuilder* out) const {
const string fullns = parseNs(dbname, cmdObj);
BSONObjBuilder explainCmdBob;
ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
// We will time how long it takes to run the commands on the shards.
Timer timer;
Strategy::CommandResult singleResult;
Status commandStat = Strategy::commandOpUnsharded(dbname,
explainCmdBob.obj(),
0,
fullns,
&singleResult);
if (!commandStat.isOK()) {
return commandStat;
}
long long millisElapsed = timer.millis();
vector shardResults;
shardResults.push_back(singleResult);
return ClusterExplain::buildExplainResult(shardResults,
ClusterExplain::kSingleShard,
millisElapsed,
out);
}
} groupCmd;
class SplitVectorCmd : public NotAllowedOnShardedCollectionCmd {
public:
SplitVectorCmd() : NotAllowedOnShardedCollectionCmd("splitVector") {}
virtual bool passOptions() const { return true; }
virtual Status checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname,
cmdObj))),
ActionType::splitVector)) {
return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
return Status::OK();
}
virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
string x = parseNs(dbName, cmdObj);
if ( ! str::startsWith( x , dbName ) ) {
errmsg = str::stream() << "doing a splitVector across dbs isn't supported via mongos";
return false;
}
return NotAllowedOnShardedCollectionCmd::run(txn,
dbName,
cmdObj,
options,
errmsg,
result);
}
virtual std::string parseNs(const string& dbname, const BSONObj& cmdObj) const {
return parseNsFullyQualified(dbname, cmdObj);
}
} splitVectorCmd;
class DistinctCmd : public PublicGridCommand {
public:
DistinctCmd() : PublicGridCommand("distinct") {}
virtual void help( stringstream &help ) const {
help << "{ distinct : 'collection name' , key : 'a.b' , query : {} }";
}
virtual bool passOptions() const { return true; }
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::find);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
bool run(OperationContext* txn,
const string& dbName ,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
const string fullns = parseNs(dbName, cmdObj);
auto status = grid.catalogCache()->getDatabase(dbName);
if (!status.isOK()) {
return appendEmptyResultSet(result, status.getStatus(), fullns);
}
shared_ptr conf = status.getValue();
if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
return passthrough(conf, cmdObj, options, result);
}
ChunkManagerPtr cm = conf->getChunkManager( fullns );
massert( 10420 , "how could chunk manager be null!" , cm );
BSONObj query = getQuery(cmdObj);
set shardIds;
cm->getShardIdsForQuery(shardIds, query);
set all;
int size = 32;
for (const ShardId& shardId : shardIds) {
const auto& shard = grid.shardRegistry()->findIfExists(shardId);
if (!shard) {
continue;
}
ShardConnection conn(shard->getConnString(), fullns);
BSONObj res;
bool ok = conn->runCommand( conf->name() , cmdObj , res, options );
conn.done();
if ( ! ok ) {
result.appendElements( res );
return false;
}
BSONObjIterator it( res["values"].embeddedObject() );
while ( it.more() ) {
BSONElement nxt = it.next();
BSONObjBuilder temp(32);
temp.appendAs( nxt , "" );
all.insert( temp.obj() );
}
}
BSONObjBuilder b( size );
int n=0;
for ( set::iterator i = all.begin() ; i != all.end(); i++ ) {
b.appendAs( i->firstElement() , b.numStr( n++ ) );
}
result.appendArray( "values" , b.obj() );
return true;
}
} disinctCmd;
class FileMD5Cmd : public PublicGridCommand {
public:
FileMD5Cmd() : PublicGridCommand("filemd5") {}
virtual void help( stringstream &help ) const {
help << " example: { filemd5 : ObjectId(aaaaaaa) , root : \"fs\" }";
}
virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
std::string collectionName = cmdObj.getStringField("root");
if (collectionName.empty())
collectionName = "fs";
collectionName += ".chunks";
return NamespaceString(dbname, collectionName).ns();
}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), ActionType::find));
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
const string fullns = parseNs(dbName, cmdObj);
auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
return passthrough( conf , cmdObj , result );
}
ChunkManagerPtr cm = conf->getChunkManager( fullns );
massert( 13091 , "how could chunk manager be null!" , cm );
if(cm->getShardKeyPattern().toBSON() == BSON("files_id" << 1)) {
BSONObj finder = BSON("files_id" << cmdObj.firstElement());
vector results;
Strategy::commandOp(dbName, cmdObj, 0, fullns, finder, &results);
verify(results.size() == 1); // querying on shard key so should only talk to one shard
BSONObj res = results.begin()->result;
result.appendElements(res);
return res["ok"].trueValue();
}
else if (cm->getShardKeyPattern().toBSON() == BSON("files_id" << 1 << "n" << 1)) {
int n = 0;
BSONObj lastResult;
while (true) {
// Theory of operation: Starting with n=0, send filemd5 command to shard
// with that chunk (gridfs chunk not sharding chunk). That shard will then
// compute a partial md5 state (passed in the "md5state" field) for all
// contiguous chunks that it has. When it runs out or hits a discontinuity
// (eg [1,2,7]) it returns what it has done so far. This is repeated as
// long as we keep getting more chunks. The end condition is when we go to
// look for chunk n and it doesn't exist. This means that the file's last
// chunk is n-1, so we return the computed md5 results.
BSONObjBuilder bb;
bb.appendElements(cmdObj);
bb.appendBool("partialOk", true);
bb.append("startAt", n);
if (!lastResult.isEmpty()){
bb.append(lastResult["md5state"]);
}
BSONObj shardCmd = bb.obj();
BSONObj finder = BSON("files_id" << cmdObj.firstElement() << "n" << n);
vector results;
try {
Strategy::commandOp(dbName, shardCmd, 0, fullns, finder, &results);
}
catch( DBException& e ){
//This is handled below and logged
Strategy::CommandResult errResult;
errResult.shardTargetId = "";
errResult.result = BSON("errmsg" << e.what() << "ok" << 0 );
results.push_back( errResult );
}
verify(results.size() == 1); // querying on shard key so should only talk to one shard
BSONObj res = results.begin()->result;
bool ok = res["ok"].trueValue();
if (!ok) {
// Add extra info to make debugging easier
result.append("failedAt", n);
result.append("sentCommand", shardCmd);
BSONForEach(e, res){
if (!str::equals(e.fieldName(), "errmsg"))
result.append(e);
}
log() << "Sharded filemd5 failed: " << result.asTempObj();
errmsg = string("sharded filemd5 failed because: ") + res["errmsg"].valuestrsafe();
return false;
}
uassert(16246, "Shard " + conf->name() + " is too old to support GridFS sharded by {files_id:1, n:1}",
res.hasField("md5state"));
lastResult = res;
int nNext = res["numChunks"].numberInt();
if (n == nNext){
// no new data means we've reached the end of the file
result.appendElements(res);
return true;
}
verify(nNext > n);
n = nNext;
}
verify(0);
}
// We could support arbitrary shard keys by sending commands to all shards but I don't think we should
errmsg = "GridFS fs.chunks collection must be sharded on either {files_id:1} or {files_id:1, n:1}";
return false;
}
} fileMD5Cmd;
class Geo2dFindNearCmd : public PublicGridCommand {
public:
Geo2dFindNearCmd() : PublicGridCommand( "geoNear" ) {}
void help(stringstream& h) const { h << "http://dochub.mongodb.org/core/geo#GeospatialIndexing-geoNearCommand"; }
virtual bool passOptions() const { return true; }
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::find);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
const string fullns = parseNs(dbName, cmdObj);
auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
return passthrough( conf , cmdObj , options, result );
}
ChunkManagerPtr cm = conf->getChunkManager( fullns );
massert( 13500 , "how could chunk manager be null!" , cm );
BSONObj query = getQuery(cmdObj);
set shardIds;
cm->getShardIdsForQuery(shardIds, query);
// We support both "num" and "limit" options to control limit
int limit = 100;
const char* limitName = cmdObj["num"].isNumber() ? "num" : "limit";
if (cmdObj[limitName].isNumber())
limit = cmdObj[limitName].numberInt();
list< shared_ptr > futures;
BSONArrayBuilder shardArray;
for (const ShardId& shardId : shardIds) {
const auto& shard = grid.shardRegistry()->findIfExists(shardId);
if (!shard) {
continue;
}
futures.push_back(Future::spawnCommand(shard->getConnString().toString(),
dbName,
cmdObj,
options));
shardArray.append(shardId);
}
multimap results; // TODO: maybe use merge-sort instead
string nearStr;
double time = 0;
double btreelocs = 0;
double nscanned = 0;
double objectsLoaded = 0;
for ( list< shared_ptr >::iterator i=futures.begin(); i!=futures.end(); i++ ) {
shared_ptr res = *i;
if ( ! res->join() ) {
errmsg = res->result()["errmsg"].String();
if (res->result().hasField("code")) {
result.append(res->result()["code"]);
}
return false;
}
if (res->result().hasField("near")) {
nearStr = res->result()["near"].String();
}
time += res->result()["stats"]["time"].Number();
if (!res->result()["stats"]["btreelocs"].eoo()) {
btreelocs += res->result()["stats"]["btreelocs"].Number();
}
nscanned += res->result()["stats"]["nscanned"].Number();
if (!res->result()["stats"]["objectsLoaded"].eoo()) {
objectsLoaded += res->result()["stats"]["objectsLoaded"].Number();
}
BSONForEach(obj, res->result()["results"].embeddedObject()) {
results.insert(make_pair(obj["dis"].Number(), obj.embeddedObject().getOwned()));
}
// TODO: maybe shrink results if size() > limit
}
result.append("ns" , fullns);
result.append("near", nearStr);
int outCount = 0;
double totalDistance = 0;
double maxDistance = 0;
{
BSONArrayBuilder sub (result.subarrayStart("results"));
for (multimap::const_iterator it(results.begin()), end(results.end()); it!= end && outCount < limit; ++it, ++outCount) {
totalDistance += it->first;
maxDistance = it->first; // guaranteed to be highest so far
sub.append(it->second);
}
sub.done();
}
{
BSONObjBuilder sub (result.subobjStart("stats"));
sub.append("time", time);
sub.append("btreelocs", btreelocs);
sub.append("nscanned", nscanned);
sub.append("objectsLoaded", objectsLoaded);
sub.append("avgDistance", (outCount == 0) ? 0: (totalDistance / outCount));
sub.append("maxDistance", maxDistance);
sub.append("shards", shardArray.arr());
sub.done();
}
return true;
}
} geo2dFindNearCmd;
class ApplyOpsCmd : public PublicGridCommand {
public:
ApplyOpsCmd() : PublicGridCommand( "applyOps" ) {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
// applyOps can do pretty much anything, so require all privileges.
RoleGraph::generateUniversalPrivileges(out);
}
virtual bool run(OperationContext* txn, const string& dbName , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) {
errmsg = "applyOps not allowed through mongos";
return false;
}
} applyOpsCmd;
class CompactCmd : public PublicGridCommand {
public:
CompactCmd() : PublicGridCommand( "compact" ) {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::compact);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
errmsg = "compact not allowed through mongos";
return false;
}
} compactCmd;
class EvalCmd : public PublicGridCommand {
public:
EvalCmd() : PublicGridCommand( "eval", "$eval" ) {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
// $eval can do pretty much anything, so require all privileges.
RoleGraph::generateUniversalPrivileges(out);
}
virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
RARELY {
warning() << "the eval command is deprecated" << startupWarningsLog;
}
// $eval isn't allowed to access sharded collections, but we need to leave the
// shard to detect that.
auto status = grid.catalogCache()->getDatabase(dbName);
if (!status.isOK()) {
return appendCommandStatus(result, status.getStatus());
}
shared_ptr conf = status.getValue();
return passthrough( conf , cmdObj , result );
}
} evalCmd;
class CmdListCollections : public PublicGridCommand {
public:
CmdListCollections() : PublicGridCommand( "listCollections" ) {}
virtual Status checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
AuthorizationSession* authzSession = AuthorizationSession::get(client);
// Check for the listCollections ActionType on the database
// or find on system.namespaces for pre 3.0 systems.
if (authzSession->isAuthorizedForActionsOnResource(
ResourcePattern::forDatabaseName(dbname),
ActionType::listCollections) ||
authzSession->isAuthorizedForActionsOnResource(
ResourcePattern::forExactNamespace(
NamespaceString(dbname, "system.namespaces")),
ActionType::find)) {
return Status::OK();
}
return Status(ErrorCodes::Unauthorized,
str::stream() << "Not authorized to create users on db: " <<
dbname);
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
auto status = grid.catalogCache()->getDatabase(dbName);
if (!status.isOK()) {
return appendEmptyResultSet(result,
status.getStatus(),
dbName + ".$cmd.listCollections");
}
shared_ptr conf = status.getValue();
bool retval = passthrough( conf, cmdObj, result );
const auto& shard = grid.shardRegistry()->findIfExists(conf->getPrimaryId());
Status storeCursorStatus = storePossibleCursor(shard->getConnString().toString(),
result.asTempObj());
if (!storeCursorStatus.isOK()) {
return appendCommandStatus(result, storeCursorStatus);
}
return retval;
}
} cmdListCollections;
class CmdListIndexes : public PublicGridCommand {
public:
CmdListIndexes() : PublicGridCommand( "listIndexes" ) {}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
string ns = parseNs( dbname, cmdObj );
ActionSet actions;
actions.addAction(ActionType::listIndexes);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
bool retval = passthrough( conf, cmdObj, result );
const auto& shard = grid.shardRegistry()->findIfExists(conf->getPrimaryId());
Status storeCursorStatus = storePossibleCursor(shard->getConnString().toString(),
result.asTempObj());
if (!storeCursorStatus.isOK()) {
return appendCommandStatus(result, storeCursorStatus);
}
return retval;
}
} cmdListIndexes;
class AvailableQueryOptions : public Command {
public:
AvailableQueryOptions(): Command("availableQueryOptions",
false ,
"availablequeryoptions") {
}
virtual bool slaveOk() const { return true; }
virtual bool isWriteCommandForConfigServer() const { return false; }
virtual Status checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
return Status::OK();
}
virtual bool run(OperationContext* txn,
const string& dbname,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
result << "options" << QueryOption_AllSupportedForSharding;
return true;
}
} availableQueryOptionsCmd;
} // namespace pub_grid_cmds
} // namespace mongo