diff --git a/jstests/sharding/database_versioning_all_commands.js b/jstests/sharding/database_versioning_all_commands.js index 5751c630d21..f3621f9b13b 100644 --- a/jstests/sharding/database_versioning_all_commands.js +++ b/jstests/sharding/database_versioning_all_commands.js @@ -501,6 +501,7 @@ let testCases = { }, listCommands: {skip: "executes locally on mongos (not sent to any remote node)"}, listDatabases: {skip: "does not forward command to primary shard"}, + listDatabasesForAllTenants: {skip: "does not forward command to primary shard"}, listIndexes: { run: { sendsDbVersion: true, diff --git a/jstests/sharding/libs/last_lts_mongos_commands.js b/jstests/sharding/libs/last_lts_mongos_commands.js index d1734daab6d..5fe8fda45ce 100644 --- a/jstests/sharding/libs/last_lts_mongos_commands.js +++ b/jstests/sharding/libs/last_lts_mongos_commands.js @@ -23,6 +23,7 @@ const commandsAddedToMongosSinceLastLTS = [ "configureCollectionBalancing", "coordinateCommitTransaction", "getClusterParameter", + "listDatabasesForAllTenants", "moveRange", "reshardCollection", "rotateCertificates", diff --git a/jstests/sharding/safe_secondary_reads_drop_recreate.js b/jstests/sharding/safe_secondary_reads_drop_recreate.js index bcb7a153fef..d6e76b1f71e 100644 --- a/jstests/sharding/safe_secondary_reads_drop_recreate.js +++ b/jstests/sharding/safe_secondary_reads_drop_recreate.js @@ -246,6 +246,7 @@ let testCases = { listCollections: {skip: "primary only"}, listCommands: {skip: "does not return user data"}, listDatabases: {skip: "primary only"}, + listDatabasesForAllTenants: {skip: "primary only"}, listIndexes: {skip: "primary only"}, listShards: {skip: "does not return user data"}, lockInfo: {skip: "primary only"}, diff --git a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js index e2658d3f54a..946b9aeb368 100644 --- a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js +++ b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js @@ -279,6 +279,7 @@ let testCases = { listCollections: {skip: "primary only"}, listCommands: {skip: "does not return user data"}, listDatabases: {skip: "primary only"}, + listDatabasesForAllTenants: {skip: "primary only"}, listIndexes: {skip: "primary only"}, listShards: {skip: "does not return user data"}, lockInfo: {skip: "primary only"}, diff --git a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js index 5e1d781ca53..1210fd5cf69 100644 --- a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js +++ b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js @@ -251,6 +251,7 @@ let testCases = { listCollections: {skip: "primary only"}, listCommands: {skip: "does not return user data"}, listDatabases: {skip: "primary only"}, + listDatabasesForAllTenants: {skip: "primary only"}, listIndexes: {skip: "primary only"}, listShards: {skip: "does not return user data"}, lockInfo: {skip: "primary only"}, diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index ebd0074f4d4..e211538d8d2 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -67,6 +67,7 @@ env.Library( 'cluster_killoperations_cmd.cpp', 'cluster_list_collections_cmd.cpp', 'cluster_list_databases_cmd.cpp', + 'cluster_list_databases_for_all_tenants_cmd.cpp', 'cluster_list_indexes_cmd.cpp', 'cluster_map_reduce_agg.cpp', 'cluster_map_reduce_cmd.cpp', @@ -113,6 +114,7 @@ env.Library( '$BUILD_DIR/mongo/db/commands/feature_compatibility_parsers', '$BUILD_DIR/mongo/db/commands/kill_common', '$BUILD_DIR/mongo/db/commands/list_databases_command', + '$BUILD_DIR/mongo/db/commands/list_databases_for_all_tenants_command', '$BUILD_DIR/mongo/db/commands/map_reduce_parser', '$BUILD_DIR/mongo/db/commands/profile_common', '$BUILD_DIR/mongo/db/commands/rename_collection_idl', diff --git a/src/mongo/s/commands/cluster_list_databases_for_all_tenants_cmd.cpp b/src/mongo/s/commands/cluster_list_databases_for_all_tenants_cmd.cpp new file mode 100644 index 00000000000..43641ec3649 --- /dev/null +++ b/src/mongo/s/commands/cluster_list_databases_for_all_tenants_cmd.cpp @@ -0,0 +1,210 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side 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. + */ + +#include "mongo/platform/basic.h" + +#include +#include + +#include "mongo/bson/util/bson_extract.h" +#include "mongo/client/read_preference.h" +#include "mongo/client/remote_command_targeter.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/list_databases_for_all_tenants_gen.h" +#include "mongo/db/multitenancy_gen.h" +#include "mongo/s/client/shard.h" +#include "mongo/s/client/shard_registry.h" +#include "mongo/s/cluster_commands_helpers.h" +#include "mongo/s/commands/strategy.h" +#include "mongo/s/grid.h" + +namespace mongo { +namespace { + +class ListDatabasesForAllTenantsCmd final + : public ListDatabasesForAllTenantsCmdVersion1Gen { +public: + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + return AllowedOnSecondary::kAlways; + } + + bool maintenanceOk() const final { + return false; + } + + bool adminOnly() const final { + return true; + } + + class Invocation final : public InvocationBaseGen { + public: + using InvocationBaseGen::InvocationBaseGen; + + bool supportsWriteConcern() const final { + return false; + } + + void doCheckAuthorization(OperationContext* opCtx) const final { + AuthorizationSession* authzSession = AuthorizationSession::get(opCtx->getClient()); + uassert(ErrorCodes::Unauthorized, + "Unauthorized", + authzSession->isAuthenticatedAsUserWithRole(RoleName("__system", "admin"))); + } + + NamespaceString ns() const final { + return NamespaceString(request().getDbName(), ""); + } + + ListDatabasesForAllTenantsReply typedRun(OperationContext* opCtx) final { + CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); + uassert(ErrorCodes::CommandNotSupported, + "Multitenancy not enabled, command not supported", + gMultitenancySupport); + + auto cmd = request(); + + // { nameOnly: bool } - Default false. + const bool nameOnly = cmd.getNameOnly(); + + auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); + + std::map> tenantToSizes; + std::map>> + tenantToDbShardInfo; + + auto shardIds = shardRegistry->getAllShardIds(opCtx); + shardIds.emplace_back(ShardId::kConfigServerId); + + // { filter: matchExpression }. + auto filteredCmd = applyReadWriteConcern( + opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmd.toBSON({}))); + + for (const ShardId& shardId : shardIds) { + auto shardStatus = shardRegistry->getShard(opCtx, shardId); + if (!shardStatus.isOK()) { + continue; + } + const auto s = std::move(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(); + const auto tenant = mongo::TenantId::parseFromBSON(dbObj["tenant"]); + + // If this is the admin db, only collect its stats from the config servers. + if (name == "admin" && !s->isConfig()) { + continue; + } + + // 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(); + + auto& sizes = tenantToSizes[tenant]; + long long& sizeSumForDbAcrossShards = sizes[name]; + if (size == 1) { + if (sizeSumForDbAcrossShards <= 1) { + sizeSumForDbAcrossShards = 1; + } + } else { + sizeSumForDbAcrossShards += size; + } + + auto& dbShardInfo = tenantToDbShardInfo[tenant]; + auto& bb = dbShardInfo[name]; + if (!bb) { + bb.reset(new BSONObjBuilder()); + } + + 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 items; + for (const auto& tenantToSizeEntry : tenantToSizes) { + const auto& tenant = tenantToSizeEntry.first; + const auto& sizes = tenantToSizeEntry.second; + + for (const auto& sizeEntry : sizes) { + const auto& name = sizeEntry.first; + const auto& size = sizeEntry.second; + + // Skip the local database, since all shards have their own independent local + if (name == NamespaceString::kLocalDb) + continue; + + ListDatabasesForAllTenantsReplyItem item(name); + if (!nameOnly) { + item.setSizeOnDisk(size); + item.setEmpty(size == 1); + item.setShards(tenantToDbShardInfo[tenant][name]->obj()); + item.setTenant(tenant); + + uassert(ErrorCodes::BadValue, + str::stream() << "Found negative 'sizeOnDisk' in: " << name, + size >= 0); + + totalSize += size; + } + + items.push_back(std::move(item)); + } + } + + ListDatabasesForAllTenantsReply reply(items); + if (!nameOnly) { + reply.setTotalSize(totalSize); + reply.setTotalSizeMb(totalSize / (1024 * 1024)); + } + + return reply; + } + }; +} listDatabasesForAllTenantsCmd; + +} // namespace +} // namespace mongo