diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2021-01-13 23:09:10 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-01-14 22:59:42 +0000 |
commit | 466870c47fce1e7c4f11453abc4c234835d35327 (patch) | |
tree | 03b92b22a6d6702f010ac718f57e4811396537cc | |
parent | f3f6b1c0a0c540f7037e5c44248b14c7a9e2e4f1 (diff) | |
download | mongo-466870c47fce1e7c4f11453abc4c234835d35327.tar.gz |
SERVER-52554 Convert ListDatabases to TypedCommand
-rw-r--r-- | src/mongo/db/commands/list_databases.cpp | 220 | ||||
-rw-r--r-- | src/mongo/db/commands/list_databases.idl | 1 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_list_databases_cmd.cpp | 255 |
3 files changed, 223 insertions, 253 deletions
diff --git a/src/mongo/db/commands/list_databases.cpp b/src/mongo/db/commands/list_databases.cpp index fa70ed6ac96..e328ebc5237 100644 --- a/src/mongo/db/commands/list_databases.cpp +++ b/src/mongo/db/commands/list_databases.cpp @@ -43,29 +43,18 @@ #include "mongo/db/storage/storage_engine.h" namespace mongo { + +// XXX: remove and put into storage api +std::intmax_t dbSize(const std::string& database); + namespace { -static const StringData kFilterField{"filter"}; -static const StringData kNameField{"name"}; -static const StringData kNameOnlyField{"nameOnly"}; // Failpoint which causes to hang "listDatabases" cmd after acquiring global lock in IS mode. MONGO_FAIL_POINT_DEFINE(hangBeforeListDatabases); -} // namespace - -using std::set; -using std::string; -using std::stringstream; -using std::vector; - -// XXX: remove and put into storage api -intmax_t dbSize(const string& database); -class CmdListDatabases : public BasicCommand { +constexpr auto kName = "name"_sd; +class CmdListDatabases final : public ListDatabasesCmdVersion1Gen<CmdListDatabases> { public: - const std::set<std::string>& apiVersions() const { - return kApiVersions1; - } - AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { return AllowedOnSecondary::kOptIn; } @@ -75,123 +64,122 @@ public: bool maintenanceOk() const final { return false; } - bool supportsWriteConcern(const BSONObj& cmd) const final { - return false; - } std::string help() const final { return "{ listDatabases:1, [filter: <filterObject>] [, nameOnly: true ] }\n" "list databases on this server"; } - /* listDatabases is always authorized, - * however the results returned will be redacted - * based on read privileges if auth is enabled - * and the current user does not have listDatabases permisison. - */ - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const final { - return Status::OK(); - } - - CmdListDatabases() : BasicCommand("listDatabases") {} - - bool run(OperationContext* opCtx, - const string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) final { - CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); - IDLParserErrorContext ctx("listDatabases"); - auto cmd = ListDatabases::parse(ctx, cmdObj); - auto* as = AuthorizationSession::get(opCtx->getClient()); - - // {nameOnly: bool} - default false. - const bool nameOnly = cmd.getNameOnly(); - - // {authorizedDatabases: bool} - Dynamic default based on permissions. - const bool authorizedDatabases = ([as](const boost::optional<bool>& authDB) { - const bool mayListAllDatabases = as->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::listDatabases); - - if (authDB) { - uassert(ErrorCodes::Unauthorized, - "Insufficient permissions to list all databases", - authDB.get() || mayListAllDatabases); - return authDB.get(); - } + class Invocation final : public InvocationBaseGen { + public: + using InvocationBaseGen::InvocationBaseGen; - // By default, list all databases if we can, otherwise - // only those we're allowed to find on. - return !mayListAllDatabases; - })(cmd.getAuthorizedDatabases()); - - // {filter: matchExpression}. - std::unique_ptr<MatchExpression> filter; - if (auto filterObj = cmd.getFilter()) { - // The collator is null because database metadata objects are compared using simple - // binary comparison. - auto expCtx = make_intrusive<ExpressionContext>( - opCtx, std::unique_ptr<CollatorInterface>(nullptr), NamespaceString(dbname)); - auto matcher = - uassertStatusOK(MatchExpressionParser::parse(filterObj.get(), std::move(expCtx))); - filter = std::move(matcher); + bool supportsWriteConcern() const final { + return false; } - vector<string> dbNames; - StorageEngine* storageEngine = getGlobalServiceContext()->getStorageEngine(); - { - Lock::GlobalLock lk(opCtx, MODE_IS); - CurOpFailpointHelpers::waitWhileFailPointEnabled( - &hangBeforeListDatabases, opCtx, "hangBeforeListDatabases", []() {}); - dbNames = storageEngine->listDatabases(); + void doCheckAuthorization(OperationContext*) const final {} + + NamespaceString ns() const final { + return NamespaceString(request().getDbName(), ""); } - vector<ListDatabasesReplyItem> items; + ListDatabasesReply typedRun(OperationContext* opCtx) final { + CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); + auto* as = AuthorizationSession::get(opCtx->getClient()); + auto cmd = request(); + + // {nameOnly: bool} - default false. + const bool nameOnly = cmd.getNameOnly(); + + // {authorizedDatabases: bool} - Dynamic default based on permissions. + const bool authorizedDatabases = ([as](const boost::optional<bool>& authDB) { + const bool mayListAllDatabases = as->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::listDatabases); + + if (authDB) { + uassert(ErrorCodes::Unauthorized, + "Insufficient permissions to list all databases", + authDB.get() || mayListAllDatabases); + return authDB.get(); + } + + // By default, list all databases if we can, otherwise + // only those we're allowed to find on. + return !mayListAllDatabases; + })(cmd.getAuthorizedDatabases()); + + // {filter: matchExpression}. + std::unique_ptr<MatchExpression> filter; + if (auto filterObj = cmd.getFilter()) { + // The collator is null because database metadata objects are compared using simple + // binary comparison. + auto expCtx = make_intrusive<ExpressionContext>( + opCtx, std::unique_ptr<CollatorInterface>(nullptr), ns()); + auto matcher = uassertStatusOK( + MatchExpressionParser::parse(filterObj.get(), std::move(expCtx))); + filter = std::move(matcher); + } - const bool filterNameOnly = filter && - filter->getCategory() == MatchExpression::MatchCategory::kLeaf && - filter->path() == kNameField; - long long totalSize = 0; - for (const auto& itemName : dbNames) { - if (authorizedDatabases && !as->isAuthorizedForAnyActionOnAnyResourceInDB(itemName)) { - // We don't have listDatabases on the cluser or find on this database. - continue; + std::vector<std::string> dbNames; + StorageEngine* storageEngine = getGlobalServiceContext()->getStorageEngine(); + { + Lock::GlobalLock lk(opCtx, MODE_IS); + CurOpFailpointHelpers::waitWhileFailPointEnabled( + &hangBeforeListDatabases, opCtx, "hangBeforeListDatabases", []() {}); + dbNames = storageEngine->listDatabases(); } - ListDatabasesReplyItem item(itemName); + std::vector<ListDatabasesReplyItem> items; - long long size = 0; - if (!nameOnly) { - // Filtering on name only should not require taking locks on filtered-out names. - if (filterNameOnly && !filter->matchesBSON(item.toBSON())) + const bool filterNameOnly = filter && + filter->getCategory() == MatchExpression::MatchCategory::kLeaf && + filter->path() == kName; + long long totalSize = 0; + for (const auto& itemName : dbNames) { + if (authorizedDatabases && + !as->isAuthorizedForAnyActionOnAnyResourceInDB(itemName)) { + // We don't have listDatabases on the cluser or find on this database. continue; - - AutoGetDb autoDb(opCtx, itemName, MODE_IS); - Database* const db = autoDb.getDb(); - if (!db) - continue; - - writeConflictRetry(opCtx, "sizeOnDisk", itemName, [&] { - size = storageEngine->sizeOnDiskForDb(opCtx, itemName); - }); - item.setSizeOnDisk(size); - item.setEmpty( - CollectionCatalog::get(opCtx)->getAllCollectionUUIDsFromDb(itemName).empty()); + } + + ListDatabasesReplyItem item(itemName); + + long long size = 0; + if (!nameOnly) { + // Filtering on name only should not require taking locks on filtered-out names. + if (filterNameOnly && !filter->matchesBSON(item.toBSON())) { + continue; + } + + AutoGetDb autoDb(opCtx, itemName, MODE_IS); + auto* const db = autoDb.getDb(); + if (!db) { + continue; + } + + writeConflictRetry(opCtx, "sizeOnDisk", itemName, [&] { + size = storageEngine->sizeOnDiskForDb(opCtx, itemName); + }); + item.setSizeOnDisk(size); + item.setEmpty(CollectionCatalog::get(opCtx) + ->getAllCollectionUUIDsFromDb(itemName) + .empty()); + } + if (!filter || filter->matchesBSON(item.toBSON())) { + totalSize += size; + items.push_back(std::move(item)); + } } - if (!filter || filter->matchesBSON(item.toBSON())) { - totalSize += size; - items.push_back(std::move(item)); + + ListDatabasesReply reply(items); + if (!nameOnly) { + reply.setTotalSize(totalSize); + reply.setTotalSizeMb(totalSize / (1024 * 1024)); } - } - ListDatabasesReply reply(items); - if (!nameOnly) { - reply.setTotalSize(totalSize); - reply.setTotalSizeMb(totalSize / (1024 * 1024)); + return reply; } - - reply.serialize(&result); - return true; - } + }; } cmdListDatabases; +} // namespace } // namespace mongo diff --git a/src/mongo/db/commands/list_databases.idl b/src/mongo/db/commands/list_databases.idl index 8342078de30..89bf17fc689 100644 --- a/src/mongo/db/commands/list_databases.idl +++ b/src/mongo/db/commands/list_databases.idl @@ -62,6 +62,7 @@ commands: listDatabases: description: "listDatabases Command" command_name: "listDatabases" + cpp_name: ListDatabasesCommand namespace: ignored api_version: "1" strict: true diff --git a/src/mongo/s/commands/cluster_list_databases_cmd.cpp b/src/mongo/s/commands/cluster_list_databases_cmd.cpp index d4a9cec8b9e..21ea0a14f2b 100644 --- a/src/mongo/s/commands/cluster_list_databases_cmd.cpp +++ b/src/mongo/s/commands/cluster_list_databases_cmd.cpp @@ -47,183 +47,164 @@ namespace mongo { namespace { -class ListDatabasesCmd : public BasicCommand { +class ListDatabasesCmd final : public ListDatabasesCmdVersion1Gen<ListDatabasesCmd> { public: - ListDatabasesCmd() : BasicCommand("listDatabases", "listdatabases") {} - - const std::set<std::string>& apiVersions() const { - return kApiVersions1; - } - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { return AllowedOnSecondary::kAlways; } - bool maintenanceOk() const override { + bool maintenanceOk() const final { return false; } - bool adminOnly() const override { + bool adminOnly() const final { return true; } + class Invocation final : public InvocationBaseGen { + public: + using InvocationBaseGen::InvocationBaseGen; - bool supportsWriteConcern(const BSONObj& cmd) const override { - return false; - } + bool supportsWriteConcern() const final { + return false; + } - std::string help() const override { - return "list databases in a cluster"; - } + void doCheckAuthorization(OperationContext*) const final {} - /* listDatabases is always authorized, - * however the results returned will be redacted - * based on read privileges if auth is enabled - * and the current user does not have listDatabases permisison. - */ - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return Status::OK(); - } + NamespaceString ns() const final { + return NamespaceString(request().getDbName(), ""); + } + ListDatabasesReply typedRun(OperationContext* opCtx) final { + CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); + auto* as = AuthorizationSession::get(opCtx->getClient()); + auto cmd = request(); + + // { nameOnly: bool } - Default false. + const bool nameOnly = cmd.getNameOnly(); + + // { authorizedDatabases: bool } - Dynamic default based on perms. + const bool authorizedDatabases = ([as](const boost::optional<bool>& authDB) { + const bool mayListAllDatabases = as->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::listDatabases); + if (authDB) { + uassert(ErrorCodes::Unauthorized, + "Insufficient permissions to list all databases", + authDB.get() || mayListAllDatabases); + return authDB.get(); + } - bool run(OperationContext* opCtx, - const std::string& dbname_unused, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); - - IDLParserErrorContext ctx("listDatabases"); - auto cmd = ListDatabases::parse(ctx, cmdObj); - auto* as = AuthorizationSession::get(opCtx->getClient()); - - // { nameOnly: bool } - Default false. - const bool nameOnly = cmd.getNameOnly(); - - // { authorizedDatabases: bool } - Dynamic default based on perms. - const bool authorizedDatabases = ([as](const boost::optional<bool>& authDB) { - const bool mayListAllDatabases = as->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::listDatabases); - if (authDB) { - uassert(ErrorCodes::Unauthorized, - "Insufficient permissions to list all databases", - authDB.get() || mayListAllDatabases); - return authDB.get(); - } + // By default, list all databases if we can, otherwise + // only those we're allowed to find on. + return !mayListAllDatabases; + })(cmd.getAuthorizedDatabases()); - // By default, list all databases if we can, otherwise - // only those we're allowed to find on. - return !mayListAllDatabases; - })(cmd.getAuthorizedDatabases()); + auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); - auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); + std::map<std::string, long long> sizes; + std::map<std::string, std::unique_ptr<BSONObjBuilder>> dbShardInfo; - std::map<std::string, long long> sizes; - std::map<std::string, std::unique_ptr<BSONObjBuilder>> dbShardInfo; + auto shardIds = shardRegistry->getAllShardIdsNoReload(); + shardIds.emplace_back(ShardId::kConfigServerId); - auto shardIds = shardRegistry->getAllShardIdsNoReload(); - shardIds.emplace_back(ShardId::kConfigServerId); + // { filter: matchExpression }. + auto filteredCmd = applyReadWriteConcern( + opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmd.toBSON({}))); - // { filter: matchExpression }. - auto filteredCmd = applyReadWriteConcern( - opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)); + for (const ShardId& shardId : shardIds) { + const auto shardStatus = shardRegistry->getShard(opCtx, shardId); + if (!shardStatus.isOK()) { + continue; + } + const auto s = shardStatus.getValue(); + + auto response = uassertStatusOK( + s->runCommandWithFixedRetryAttempts(opCtx, + ReadPreferenceSetting::get(opCtx), + "admin", + filteredCmd, + Shard::RetryPolicy::kIdempotent)); + uassertStatusOK(response.commandStatus); + BSONObj x = std::move(response.response); + + BSONObjIterator j(x["databases"].Obj()); + while (j.more()) { + BSONObj dbObj = j.next().Obj(); + + const auto name = dbObj["name"].String(); + + // If this is the admin db, only collect its stats from the config servers. + if (name == "admin" && !s->isConfig()) { + continue; + } - for (const ShardId& shardId : shardIds) { - const auto shardStatus = shardRegistry->getShard(opCtx, shardId); - if (!shardStatus.isOK()) { - continue; - } - const auto s = shardStatus.getValue(); + // We don't collect config server info for dbs other than "admin" and "config". + if (s->isConfig() && name != "config" && name != "admin") { + continue; + } + + const long long size = dbObj["sizeOnDisk"].numberLong(); + + long long& sizeSumForDbAcrossShards = sizes[name]; + if (size == 1) { + if (sizeSumForDbAcrossShards <= 1) { + sizeSumForDbAcrossShards = 1; + } + } else { + sizeSumForDbAcrossShards += size; + } - auto response = uassertStatusOK( - s->runCommandWithFixedRetryAttempts(opCtx, - ReadPreferenceSetting::get(opCtx), - "admin", - filteredCmd, - Shard::RetryPolicy::kIdempotent)); - uassertStatusOK(response.commandStatus); - BSONObj x = std::move(response.response); + auto& bb = dbShardInfo[name]; + if (!bb) { + bb.reset(new BSONObjBuilder()); + } - BSONObjIterator j(x["databases"].Obj()); - while (j.more()) { - BSONObj dbObj = j.next().Obj(); + bb->append(s->getId().toString(), size); + } + } - const auto name = dbObj["name"].String(); + // Now that we have aggregated results for all the shards, convert to a response, + // and compute total sizes. + long long totalSize = 0; + std::vector<ListDatabasesReplyItem> items; + for (const auto& sizeEntry : sizes) { + const auto& name = sizeEntry.first; + const long long size = sizeEntry.second; - // If this is the admin db, only collect its stats from the config servers. - if (name == "admin" && !s->isConfig()) { + // Skip the local database, since all shards have their own independent local + if (name == NamespaceString::kLocalDb) continue; - } - // We don't collect config server info for dbs other than "admin" and "config". - if (s->isConfig() && name != "config" && name != "admin") { + if (authorizedDatabases && !as->isAuthorizedForAnyActionOnAnyResourceInDB(name)) { + // We don't have listDatabases on the cluser or find on this database. continue; } - const long long size = dbObj["sizeOnDisk"].numberLong(); + ListDatabasesReplyItem item(name); + if (!nameOnly) { + item.setSizeOnDisk(size); + item.setEmpty(size == 1); + item.setShards(dbShardInfo[name]->obj()); - long long& sizeSumForDbAcrossShards = sizes[name]; - if (size == 1) { - if (sizeSumForDbAcrossShards <= 1) { - sizeSumForDbAcrossShards = 1; - } - } else { - sizeSumForDbAcrossShards += size; - } + uassert(ErrorCodes::BadValue, + str::stream() << "Found negative 'sizeOnDisk' in: " << name, + size >= 0); - auto& bb = dbShardInfo[name]; - if (!bb) { - bb.reset(new BSONObjBuilder()); + totalSize += size; } - bb->append(s->getId().toString(), size); - } - } - - // Now that we have aggregated results for all the shards, convert to a response, - // and compute total sizes. - long long totalSize = 0; - std::vector<ListDatabasesReplyItem> items; - for (const auto& sizeEntry : sizes) { - const auto& name = sizeEntry.first; - const long long size = sizeEntry.second; - - // Skip the local database, since all shards have their own independent local - if (name == NamespaceString::kLocalDb) - continue; - - if (authorizedDatabases && !as->isAuthorizedForAnyActionOnAnyResourceInDB(name)) { - // We don't have listDatabases on the cluser or find on this database. - continue; + items.push_back(std::move(item)); } - ListDatabasesReplyItem item(name); + ListDatabasesReply reply(items); if (!nameOnly) { - item.setSizeOnDisk(size); - item.setEmpty(size == 1); - item.setShards(dbShardInfo[name]->obj()); - - uassert(ErrorCodes::BadValue, - str::stream() << "Found negative 'sizeOnDisk' in: " << name, - size >= 0); - - totalSize += size; + reply.setTotalSize(totalSize); + reply.setTotalSizeMb(totalSize / (1024 * 1024)); } - items.push_back(std::move(item)); - } - - ListDatabasesReply reply(items); - if (!nameOnly) { - reply.setTotalSize(totalSize); - reply.setTotalSizeMb(totalSize / (1024 * 1024)); + return reply; } - - reply.serialize(&result); - return true; - } - + }; } listDatabasesCmd; } // namespace |