diff options
25 files changed, 884 insertions, 138 deletions
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js index 0fe746c8047..d18e14b19e3 100644 --- a/jstests/core/views/views_all_commands.js +++ b/jstests/core/views/views_all_commands.js @@ -143,6 +143,7 @@ let viewsCommandTests = { _recvChunkStatus: {skip: isAnInternalCommand}, _shardsvrAbortReshardCollection: {skip: isAnInternalCommand}, _shardsvrCloneCatalogData: {skip: isAnInternalCommand}, + _shardsvrCompactStructuredEncryptionData: {skip: isAnInternalCommand}, _shardsvrDropCollection: {skip: isAnInternalCommand}, _shardsvrDropCollectionIfUUIDNotMatching: {skip: isUnrelated}, _shardsvrDropCollectionParticipant: {skip: isAnInternalCommand}, @@ -244,11 +245,7 @@ let viewsCommandTests = { commitReshardCollection: {skip: isUnrelated}, commitTransaction: {skip: isUnrelated}, compact: {command: {compact: "view", force: true}, expectFailure: true, skipSharded: true}, - compactStructuredEncryptionData: { - command: {compactStructuredEncryptionData: "view", compactionTokens: {}}, - expectFailure: true, - skipSharded: true - }, + compactStructuredEncryptionData: {skip: isUnrelated}, configureFailPoint: {skip: isUnrelated}, configureCollectionAutoSplitter: { skip: isUnrelated diff --git a/jstests/replsets/db_reads_while_recovering_all_commands.js b/jstests/replsets/db_reads_while_recovering_all_commands.js index 6a71fb16347..279d167edba 100644 --- a/jstests/replsets/db_reads_while_recovering_all_commands.js +++ b/jstests/replsets/db_reads_while_recovering_all_commands.js @@ -78,6 +78,7 @@ const allCommands = { _shardsvrAbortReshardCollection: {skip: isPrimaryOnly}, _shardsvrCleanupReshardCollection: {skip: isPrimaryOnly}, _shardsvrCloneCatalogData: {skip: isPrimaryOnly}, + _shardsvrCompactStructuredEncryptionData: {skip: isPrimaryOnly}, _shardsvrCommitReshardCollection: {skip: isPrimaryOnly}, _shardsvrDropCollection: {skip: isPrimaryOnly}, _shardsvrCreateCollection: {skip: isPrimaryOnly}, diff --git a/jstests/replsets/tenant_migration_concurrent_writes_on_donor.js b/jstests/replsets/tenant_migration_concurrent_writes_on_donor.js index bda2513e908..49eb4baeb71 100644 --- a/jstests/replsets/tenant_migration_concurrent_writes_on_donor.js +++ b/jstests/replsets/tenant_migration_concurrent_writes_on_donor.js @@ -539,6 +539,7 @@ const testCases = { _recvChunkStart: {skip: isNotRunOnUserDatabase}, _recvChunkStatus: {skip: isNotRunOnUserDatabase}, _shardsvrCloneCatalogData: {skip: isNotRunOnUserDatabase}, + _shardsvrCompactStructuredEncryptionData: {skip: isOnlySupportedOnShardedCluster}, _shardsvrCreateCollection: {skip: isOnlySupportedOnShardedCluster}, _shardsvrCreateCollectionParticipant: {skip: isOnlySupportedOnShardedCluster}, _shardsvrMovePrimary: {skip: isNotRunOnUserDatabase}, diff --git a/jstests/sharding/compact_structured_encryption_data_coordinator.js b/jstests/sharding/compact_structured_encryption_data_coordinator.js new file mode 100644 index 00000000000..efd17674749 --- /dev/null +++ b/jstests/sharding/compact_structured_encryption_data_coordinator.js @@ -0,0 +1,110 @@ +// Basic test that the CompactStructuredEncryptionDataCoordinator runs. +// @tags: [requires_sharding,requires_fcv_60] + +(function() { +'use strict'; + +load('jstests/fle2/libs/encrypted_client_util.js'); + +if (!TestData.setParameters.featureFlagFLE2) { + jsTest.log('Skipping test because feature flag is not enabled'); + return; +} + +const options = { + mongos: 1, + config: 1, + shards: 1, + rs: {nodes: [{}]}, +}; + +const kHaveAuditing = buildInfo().modules.includes("enterprise"); +if (kHaveAuditing) { + jsTest.log('Including test for audit events since this is an enterprise build'); + const nodeOpts = options.rs.nodes[0]; + nodeOpts.auditDestination = 'file'; + nodeOpts.auditPath = MongoRunner.dataPath + '/audit.log'; + nodeOpts.auditFormat = 'JSON'; +} + +const st = new ShardingTest(options); + +const admin = st.s0.getDB('admin'); + +// Setup collection with encrypted fields and a compactible metadata collection. +const encryptedFields = { + fields: [ + {path: "firstName", bsonType: "string", queries: {"queryType": "equality"}}, + {path: "a.b.c", bsonType: "int", queries: {"queryType": "equality"}}, + ] +}; + +const client = new EncryptedClient(st.s0, 'test'); +const test = client.getDB('test'); + +assert.commandWorked( + client.createEncryptionCollection("encrypted", {encryptedFields: encryptedFields})); +assert.commandWorked(test.createCollection("unencrypted")); + +assert.commandFailedWithCode(test.unencrypted.compact(), ErrorCodes.BadValue); + +const reply = assert.commandWorked(test.encrypted.compact()); +jsTest.log(reply); + +// Validate dummy data we expect the placeholder compaction algorithm to return. +assert.eq(reply.stats.ecoc.read, 1); +assert.eq(reply.stats.ecoc.deleted, 2); + +assert.eq(reply.stats.ecc.read, 3); +assert.eq(reply.stats.ecc.inserted, 4); +assert.eq(reply.stats.ecc.updated, 5); +assert.eq(reply.stats.ecc.deleted, 6); + +assert.eq(reply.stats.esc.read, 7); +assert.eq(reply.stats.esc.inserted, 8); +assert.eq(reply.stats.esc.updated, 9); +assert.eq(reply.stats.esc.deleted, 10); + +// The eccoc collection is gone, so we should return quickly with no work done. +const nowork = assert.commandWorked(test.encrypted.compact()); +jsTest.log(nowork); + +assert.eq(nowork.stats.ecoc.read, 0); +assert.eq(nowork.stats.ecoc.deleted, 0); + +assert.eq(nowork.stats.ecc.read, 0); +assert.eq(nowork.stats.ecc.inserted, 0); +assert.eq(nowork.stats.ecc.updated, 0); +assert.eq(nowork.stats.ecc.deleted, 0); + +assert.eq(nowork.stats.esc.read, 0); +assert.eq(nowork.stats.esc.inserted, 0); +assert.eq(nowork.stats.esc.updated, 0); +assert.eq(nowork.stats.esc.deleted, 0); + +if (kHaveAuditing) { + jsTest.log('Verifying audit contents'); + + // Check the audit log for the rename/drop events. + const audit = cat(options.rs.nodes[0].auditPath) + .split('\n') + .filter((l) => l !== '') + .map((l) => JSON.parse(l)); + jsTest.log('Audit Log: ' + tojson(audit)); + + const renameEvents = audit.filter((ev) => (ev.atype === 'renameCollection') && + (ev.param.old === 'test.fle2.encrypted.ecoc')); + assert.eq(renameEvents.length, 1, 'Invalid number of rename events: ' + tojson(renameEvents)); + assert.eq(renameEvents[0].result, ErrorCodes.OK); + const tempNSS = renameEvents[0].param.new; + + const dropEvents = + audit.filter((ev) => (ev.atype === 'dropCollection') && (ev.param.ns === tempNSS)); + assert.eq(dropEvents.length, 1, 'Invalid number of drop events: ' + tojson(dropEvents)); + assert.eq(dropEvents[0].result, ErrorCodes.OK); +} + +st.stop(); + +jsTest.log(reply); +})(); diff --git a/jstests/sharding/database_versioning_all_commands.js b/jstests/sharding/database_versioning_all_commands.js index 46ddccfe4fe..edd95248a8a 100644 --- a/jstests/sharding/database_versioning_all_commands.js +++ b/jstests/sharding/database_versioning_all_commands.js @@ -300,6 +300,7 @@ let testCases = { commitReshardCollection: {skip: "always targets the config server"}, commitTransaction: {skip: "unversioned and uses special targetting rules"}, compact: {skip: "not allowed through mongos"}, + compactStructuredEncryptionData: {skip: "requires encrypted collections"}, configureCollectionBalancing: {skip: "always targets the config server"}, configureFailPoint: {skip: "executes locally on mongos (not sent to any remote node)"}, connPoolStats: {skip: "executes locally on mongos (not sent to any remote node)"}, diff --git a/jstests/sharding/libs/last_lts_mongos_commands.js b/jstests/sharding/libs/last_lts_mongos_commands.js index 86937f182db..90eb308fa4e 100644 --- a/jstests/sharding/libs/last_lts_mongos_commands.js +++ b/jstests/sharding/libs/last_lts_mongos_commands.js @@ -16,6 +16,7 @@ const commandsAddedToMongosSinceLastLTS = [ "appendOplogNote", "cleanupReshardCollection", "commitReshardCollection", + "compactStructuredEncryptionData", "configureCollectionBalancing", "moveRange", "reshardCollection", diff --git a/jstests/sharding/read_write_concern_defaults_application.js b/jstests/sharding/read_write_concern_defaults_application.js index 7642ce34e25..b7804ac5e58 100644 --- a/jstests/sharding/read_write_concern_defaults_application.js +++ b/jstests/sharding/read_write_concern_defaults_application.js @@ -147,6 +147,7 @@ let testCases = { _shardsvrCleanupReshardCollection: {skip: "internal command"}, _shardsvrCloneCatalogData: {skip: "internal command"}, _shardsvrCommitReshardCollection: {skip: "internal command"}, + _shardsvrCompactStructuredEncryptionData: {skip: "internal command"}, _shardsvrCreateCollection: {skip: "internal command"}, _shardsvrCreateCollectionParticipant: {skip: "internal command"}, _shardsvrDropCollection: {skip: "internal command"}, diff --git a/jstests/sharding/safe_secondary_reads_drop_recreate.js b/jstests/sharding/safe_secondary_reads_drop_recreate.js index 4835739324b..e7fd5f85963 100644 --- a/jstests/sharding/safe_secondary_reads_drop_recreate.js +++ b/jstests/sharding/safe_secondary_reads_drop_recreate.js @@ -82,6 +82,7 @@ let testCases = { _killOperations: {skip: "does not return user data"}, _mergeAuthzCollections: {skip: "primary only"}, _migrateClone: {skip: "primary only"}, + _shardsvrCompactStructuredEncryptionData: {skip: "primary only"}, _shardsvrMovePrimary: {skip: "primary only"}, _shardsvrMoveRange: {skip: "primary only"}, _recvChunkAbort: {skip: "primary only"}, @@ -136,6 +137,7 @@ let testCases = { commitReshardCollection: {skip: "primary only"}, commitTransaction: {skip: "primary only"}, compact: {skip: "does not return user data"}, + compactStructuredEncryptionData: {skip: "does not return user data"}, configureCollectionAutoSplitter: { skip: "does not return user data" }, // TODO SERVER-62374: remove this once 5.3 becomes last continuos release 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 ee81222de94..da5b4cb9d0d 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 @@ -92,6 +92,7 @@ let testCases = { _killOperations: {skip: "does not return user data"}, _mergeAuthzCollections: {skip: "primary only"}, _migrateClone: {skip: "primary only"}, + _shardsvrCompactStructuredEncryptionData: {skip: "primary only"}, _shardsvrMovePrimary: {skip: "primary only"}, _shardsvrMoveRange: {skip: "primary only"}, _recvChunkAbort: {skip: "primary only"}, @@ -156,6 +157,7 @@ let testCases = { collMod: {skip: "primary only"}, collStats: {skip: "does not return user data"}, compact: {skip: "does not return user data"}, + compactStructuredEncryptionData: {skip: "does not return user data"}, configureFailPoint: {skip: "does not return user data"}, connPoolStats: {skip: "does not return user data"}, connPoolSync: {skip: "does not return user data"}, diff --git a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js index 23eac4fe2c2..24991db2b9e 100644 --- a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js +++ b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js @@ -84,6 +84,7 @@ let testCases = { _killOperations: {skip: "does not return user data"}, _mergeAuthzCollections: {skip: "primary only"}, _migrateClone: {skip: "primary only"}, + _shardsvrCompactStructuredEncryptionData: {skip: "primary only"}, _shardsvrMovePrimary: {skip: "primary only"}, _shardsvrMoveRange: {skip: "primary only"}, _recvChunkAbort: {skip: "primary only"}, @@ -139,6 +140,7 @@ let testCases = { commitReshardCollection: {skip: "primary only"}, commitTransaction: {skip: "primary only"}, compact: {skip: "does not return user data"}, + compactStructuredEncryptionData: {skip: "does not return user data"}, configureCollectionAutoSplitter: { skip: "does not return user data" }, // TODO SERVER-62374: remove this once 5.3 becomes last continuos release diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 37055dd8a94..db92db44bac 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -85,6 +85,8 @@ env.Library( source=[ 'end_sessions_command.cpp', 'fail_point_cmd.cpp', + 'fle2_compact.cpp', + 'fle2_compact.idl', 'generic.cpp', 'generic.idl', 'hashcmd.cpp', @@ -104,6 +106,7 @@ env.Library( ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/bson/mutable/mutable_bson', + '$BUILD_DIR/mongo/crypto/encrypted_field_config', '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/authprivilege', '$BUILD_DIR/mongo/db/commands', @@ -114,9 +117,11 @@ env.Library( '$BUILD_DIR/mongo/db/logical_session_id', '$BUILD_DIR/mongo/db/logical_session_id_helpers', '$BUILD_DIR/mongo/db/mongohasher', + '$BUILD_DIR/mongo/db/namespace_string', '$BUILD_DIR/mongo/db/ops/write_ops_parsers', '$BUILD_DIR/mongo/db/server_options_core', '$BUILD_DIR/mongo/idl/basic_types', + '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/idl/server_parameter', '$BUILD_DIR/mongo/rpc/message', 'test_commands_enabled', @@ -227,22 +232,6 @@ env.Library( ) env.Library( - target="fle2_compact", - source=[ - 'fle2_compact.cpp', - 'fle2_compact.idl', - ], - LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/crypto/encrypted_field_config', - '$BUILD_DIR/mongo/db/auth/auth', - '$BUILD_DIR/mongo/db/catalog/catalog_helpers', - '$BUILD_DIR/mongo/db/catalog/collection_catalog', - '$BUILD_DIR/mongo/db/catalog_raii', - '$BUILD_DIR/mongo/db/commands', - ] -) - -env.Library( target="mongod_fsync", source=[ "fsync.cpp", @@ -545,6 +534,7 @@ env.Library( "dbhash.cpp", "driverHelpers.cpp", "internal_rename_if_options_and_indexes_match_cmd.cpp", + "fle2_compact_cmd.cpp", "map_reduce_command.cpp", "oplog_application_checks.cpp", "oplog_note.cpp", @@ -614,7 +604,6 @@ env.Library( '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/util/net/ssl_manager', 'core', - 'fle2_compact', 'kill_common', 'map_reduce_agg', 'mongod_fcv', diff --git a/src/mongo/db/commands/fle2_compact.cpp b/src/mongo/db/commands/fle2_compact.cpp index 7588cbc6735..24256c6bc54 100644 --- a/src/mongo/db/commands/fle2_compact.cpp +++ b/src/mongo/db/commands/fle2_compact.cpp @@ -27,70 +27,14 @@ * it in the license file. */ -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - #include "mongo/platform/basic.h" #include "mongo/db/commands/fle2_compact.h" #include "mongo/crypto/encryption_fields_gen.h" -#include "mongo/db/auth/authorization_session.h" #include "mongo/db/catalog/collection_catalog.h" -#include "mongo/db/catalog/rename_collection.h" -#include "mongo/db/catalog_raii.h" -#include "mongo/db/commands.h" -#include "mongo/logv2/log.h" namespace mongo { -namespace { - -class CompactStructuredEncryptionDataCmd final - : public TypedCommand<CompactStructuredEncryptionDataCmd> { -public: - using Request = CompactStructuredEncryptionData; - using Reply = CompactStructuredEncryptionData::Reply; - using TC = TypedCommand<CompactStructuredEncryptionDataCmd>; - - class Invocation final : public TC::InvocationBase { - public: - using TC::InvocationBase::InvocationBase; - using TC::InvocationBase::request; - - Reply typedRun(OperationContext* opCtx) { - auto swCompactStats = compactEncryptedCompactionCollection(opCtx, request()); - uassertStatusOK(swCompactStats); - return Reply(swCompactStats.getValue()); - } - - private: - bool supportsWriteConcern() const final { - return false; - } - - void doCheckAuthorization(OperationContext* opCtx) const final { - auto* as = AuthorizationSession::get(opCtx->getClient()); - uassert(ErrorCodes::Unauthorized, - "Not authorized to compact structured encryption data", - as->isAuthorizedForActionsOnResource( - ResourcePattern::forExactNamespace(request().getNamespace()), - ActionType::compactStructuredEncryptionData)); - } - - NamespaceString ns() const final { - return request().getNamespace(); - } - }; - - typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { - return BasicCommand::AllowedOnSecondary::kNever; - } - - bool adminOnly() const final { - return false; - } -} compactStructuredEncryptionDataCmd; - -} // namespace StatusWith<EncryptedStateCollectionsNamespaces> EncryptedStateCollectionsNamespaces::createFromDataCollection(const Collection& edc) { @@ -129,60 +73,4 @@ EncryptedStateCollectionsNamespaces::createFromDataCollection(const Collection& return namespaces; } -StatusWith<CompactStats> compactEncryptedCompactionCollection( - OperationContext* opCtx, const CompactStructuredEncryptionData& request) { - mongo::ECOCStats ecocStats(0, 0); - mongo::ECStats eccStats(0, 0, 0, 0); - mongo::ECStats escStats(0, 0, 0, 0); - const auto& edcNss = request.getNamespace(); - - LOGV2(6319900, "Compacting the encrypted compaction collection", "namespace"_attr = edcNss); - - AutoGetDb autoDb(opCtx, edcNss.db(), MODE_IX); - if (!autoDb.getDb()) { - return Status(ErrorCodes::NamespaceNotFound, str::stream() << "database does not exist"); - } - - auto catalog = CollectionCatalog::get(opCtx); - Lock::CollectionLock edcLock(opCtx, edcNss, MODE_IS); - - // Check the data collection exists and is not a view - auto edc = catalog->lookupCollectionByNamespace(opCtx, edcNss); - if (!edc) { - if (catalog->lookupView(opCtx, edcNss)) { - return Status(ErrorCodes::CommandNotSupportedOnView, - "cannot compact structured encryption data on a view"); - } - return Status(ErrorCodes::NamespaceNotFound, "collection does not exist"); - } - - uassert(6319903, "Feature flag FLE2 is not enabled", gFeatureFlagFLE2.isEnabledAndIgnoreFCV()); - - auto swNamespaces = EncryptedStateCollectionsNamespaces::createFromDataCollection(*edc.get()); - if (!swNamespaces.isOK()) { - return swNamespaces.getStatus(); - } - auto& namespaces = swNamespaces.getValue(); - - auto ecoc = catalog->lookupCollectionByNamespace(opCtx, namespaces.ecocNss); - auto ecocRename = catalog->lookupCollectionByNamespace(opCtx, namespaces.ecocRenameNss); - - if (ecoc && !ecocRename) { - LOGV2(6319901, - "Renaming the encrypted compaction collection", - "ecocNss"_attr = namespaces.ecocNss, - "ecocRenameNss"_attr = namespaces.ecocRenameNss); - RenameCollectionOptions renameOpts; - validateAndRunRenameCollection( - opCtx, namespaces.ecocNss, namespaces.ecocRenameNss, renameOpts); - ecoc.reset(); - } - - - LOGV2(6319902, - "Done compacting the encrypted compaction collection", - "namespace"_attr = request.getNamespace()); - return CompactStats(ecocStats, eccStats, escStats); -} - } // namespace mongo diff --git a/src/mongo/db/commands/fle2_compact.h b/src/mongo/db/commands/fle2_compact.h index 35308325169..b994e22a5a2 100644 --- a/src/mongo/db/commands/fle2_compact.h +++ b/src/mongo/db/commands/fle2_compact.h @@ -30,8 +30,8 @@ #pragma once #include "mongo/base/status_with.h" +#include "mongo/db/catalog/collection.h" #include "mongo/db/commands/fle2_compact_gen.h" -#include "mongo/db/operation_context.h" namespace mongo { @@ -46,7 +46,4 @@ struct EncryptedStateCollectionsNamespaces { NamespaceString ecocRenameNss; }; -StatusWith<CompactStats> compactEncryptedCompactionCollection( - OperationContext* opCtx, const CompactStructuredEncryptionData& request); - } // namespace mongo diff --git a/src/mongo/db/commands/fle2_compact_cmd.cpp b/src/mongo/db/commands/fle2_compact_cmd.cpp new file mode 100644 index 00000000000..ba3d8a7ed22 --- /dev/null +++ b/src/mongo/db/commands/fle2_compact_cmd.cpp @@ -0,0 +1,145 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage + +#include "mongo/platform/basic.h" + +#include "mongo/db/commands/fle2_compact.h" + +#include "mongo/crypto/encryption_fields_gen.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/catalog/collection_catalog.h" +#include "mongo/db/catalog/rename_collection.h" +#include "mongo/db/catalog_raii.h" +#include "mongo/db/commands.h" +#include "mongo/logv2/log.h" + +namespace mongo { +namespace { + +CompactStats compactEncryptedCompactionCollection(OperationContext* opCtx, + const CompactStructuredEncryptionData& request) { + mongo::ECOCStats ecocStats(0, 0); + mongo::ECStats eccStats(0, 0, 0, 0); + mongo::ECStats escStats(0, 0, 0, 0); + const auto& edcNss = request.getNamespace(); + + LOGV2(6319900, "Compacting the encrypted compaction collection", "namespace"_attr = edcNss); + + AutoGetDb autoDb(opCtx, edcNss.db(), MODE_IX); + uassert(ErrorCodes::NamespaceNotFound, + str::stream() << "Database '" << edcNss.db() << "' does not exist", + autoDb.getDb()); + + auto catalog = CollectionCatalog::get(opCtx); + Lock::CollectionLock edcLock(opCtx, edcNss, MODE_IS); + + // Check the data collection exists and is not a view + auto edc = catalog->lookupCollectionByNamespace(opCtx, edcNss); + if (!edc) { + uassert(ErrorCodes::CommandNotSupportedOnView, + "Cannot compact structured encryption data on a view", + !catalog->lookupView(opCtx, edcNss)); + uasserted(ErrorCodes::NamespaceNotFound, + str::stream() << "Collection '" << edcNss << "' does not exist"); + } + + uassert(6319903, "Feature flag FLE2 is not enabled", gFeatureFlagFLE2.isEnabledAndIgnoreFCV()); + + auto namespaces = + uassertStatusOK(EncryptedStateCollectionsNamespaces::createFromDataCollection(*edc.get())); + + + auto ecoc = catalog->lookupCollectionByNamespace(opCtx, namespaces.ecocNss); + auto ecocRename = catalog->lookupCollectionByNamespace(opCtx, namespaces.ecocRenameNss); + + if (ecoc && !ecocRename) { + LOGV2(6319901, + "Renaming the encrypted compaction collection", + "ecocNss"_attr = namespaces.ecocNss, + "ecocRenameNss"_attr = namespaces.ecocRenameNss); + RenameCollectionOptions renameOpts; + validateAndRunRenameCollection( + opCtx, namespaces.ecocNss, namespaces.ecocRenameNss, renameOpts); + ecoc.reset(); + } + + LOGV2(6319902, + "Done compacting the encrypted compaction collection", + "namespace"_attr = request.getNamespace()); + return CompactStats(ecocStats, eccStats, escStats); +} + +class CompactStructuredEncryptionDataCmd final + : public TypedCommand<CompactStructuredEncryptionDataCmd> { +public: + using Request = CompactStructuredEncryptionData; + using Reply = CompactStructuredEncryptionData::Reply; + using TC = TypedCommand<CompactStructuredEncryptionDataCmd>; + + class Invocation final : public TC::InvocationBase { + public: + using TC::InvocationBase::InvocationBase; + using TC::InvocationBase::request; + + Reply typedRun(OperationContext* opCtx) { + return Reply(compactEncryptedCompactionCollection(opCtx, request())); + } + + private: + bool supportsWriteConcern() const final { + return false; + } + + void doCheckAuthorization(OperationContext* opCtx) const final { + auto* as = AuthorizationSession::get(opCtx->getClient()); + uassert(ErrorCodes::Unauthorized, + "Not authorized to compact structured encryption data", + as->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace(request().getNamespace()), + ActionType::compactStructuredEncryptionData)); + } + + NamespaceString ns() const final { + return request().getNamespace(); + } + }; + + typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + return BasicCommand::AllowedOnSecondary::kNever; + } + + bool adminOnly() const final { + return false; + } +} compactStructuredEncryptionDataCmd; + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/namespace_string.cpp b/src/mongo/db/namespace_string.cpp index ab4a511bc69..bd9d3a68d93 100644 --- a/src/mongo/db/namespace_string.cpp +++ b/src/mongo/db/namespace_string.cpp @@ -154,6 +154,9 @@ const NamespaceString NamespaceString::kConfigsvrCoordinatorsNamespace( const NamespaceString NamespaceString::kUserWritesCriticalSectionsNamespace( NamespaceString::kConfigDb, "user_writes_critical_sections"); +const NamespaceString NamespaceString::kCompactStructuredEncryptionCoordinatorNamespace( + NamespaceString::kConfigDb, "compact_structured_encryption_coordinator"); + bool NamespaceString::isListCollectionsCursorNS() const { return coll() == listCollectionsCursorCol; } diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h index f511a6a3f04..6a9418f1775 100644 --- a/src/mongo/db/namespace_string.h +++ b/src/mongo/db/namespace_string.h @@ -213,6 +213,9 @@ public: // Namespace used during the recovery procedure for the config server. static const NamespaceString kConfigsvrRestoreNamespace; + // Namespace used for CompactParticipantCoordinator service. + static const NamespaceString kCompactStructuredEncryptionCoordinatorNamespace; + /** * Constructs an empty NamespaceString. */ diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript index 38cae8e2be5..96f60ade98d 100644 --- a/src/mongo/db/s/SConscript +++ b/src/mongo/db/s/SConscript @@ -320,6 +320,8 @@ env.Library( 'collmod_coordinator.cpp', 'collmod_coordinator_document.idl', 'collmod_coordinator_pre60_compatible.cpp', + 'compact_structured_encryption_data_coordinator.cpp', + 'compact_structured_encryption_data_coordinator.idl', 'config/set_cluster_parameter_coordinator.cpp', 'config/set_cluster_parameter_coordinator_document.idl', 'config/set_user_write_block_mode_coordinator.cpp', @@ -399,6 +401,7 @@ env.Library( 'shardsvr_collmod_command.cpp', 'shardsvr_collmod_participant_command.cpp', 'shardsvr_commit_reshard_collection_command.cpp', + 'shardsvr_compact_structured_encryption_data_command.cpp', 'shardsvr_create_collection_command.cpp', 'shardsvr_create_collection_participant_command.cpp', 'shardsvr_drop_collection_command.cpp', @@ -428,6 +431,7 @@ env.Library( '$BUILD_DIR/mongo/db/commands/core', '$BUILD_DIR/mongo/db/commands/create_command', '$BUILD_DIR/mongo/db/commands/mongod_fcv', + '$BUILD_DIR/mongo/db/commands/rename_collection_idl', '$BUILD_DIR/mongo/db/commands/server_status', '$BUILD_DIR/mongo/db/commands/test_commands_enabled', '$BUILD_DIR/mongo/db/commands/txn_cmd_request', diff --git a/src/mongo/db/s/compact_structured_encryption_data_coordinator.cpp b/src/mongo/db/s/compact_structured_encryption_data_coordinator.cpp new file mode 100644 index 00000000000..ba8c48cbef1 --- /dev/null +++ b/src/mongo/db/s/compact_structured_encryption_data_coordinator.cpp @@ -0,0 +1,159 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding + +#include "mongo/platform/basic.h" + +#include "mongo/db/s/compact_structured_encryption_data_coordinator.h" + +#include "mongo/base/checked_cast.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/catalog/collection_catalog.h" +#include "mongo/db/commands/rename_collection_gen.h" +#include "mongo/db/dbdirectclient.h" +#include "mongo/db/drop_gen.h" +#include "mongo/db/persistent_task_store.h" +#include "mongo/logv2/log.h" + +namespace mongo { +namespace { + +const auto kMajorityWriteConcern = BSON("writeConcern" << BSON("w" + << "majority")); +/** + * Issue a simple success/fail command such as renameCollection or drop + * using majority write concern. + */ +template <typename Request> +void doRunCommand(OperationContext* opCtx, StringData dbname, const Request& request) { + DBDirectClient client(opCtx); + BSONObj cmd = request.toBSON(kMajorityWriteConcern); + auto reply = client.runCommand(OpMsgRequest::fromDBAndBody(dbname, cmd))->getCommandReply(); + uassertStatusOK(getStatusFromCommandResult(reply)); +} + +void doRenameOperation(const CompactStructuredEncryptionDataState& state) { + const auto& ecocNss = state.getEcocNss(); + auto opCtx = cc().makeOperationContext(); + auto catalog = CollectionCatalog::get(opCtx.get()); + auto uuid = catalog->lookupUUIDByNSS(opCtx.get(), ecocNss); + if (!uuid) { + // Nothing to rename. + LOGV2_DEBUG(6350492, + 5, + "Skipping rename stage as there is no source collection", + "ecocNss"_attr = ecocNss); + return; + } else if (uuid.get() != state.getEcocUuid()) { + // The generation of the collection to be compacted is different than the one which was + // requested. + LOGV2_DEBUG(6350491, + 5, + "Skipping rename of mismatched collection uuid", + "ecocNss"_attr = ecocNss, + "uuid"_attr = uuid.get(), + "expectedUUID"_attr = state.getEcocUuid()); + return; + } + + // Otherwise, perform the rename so long as the target namespace does not exist. + RenameCollectionCommand cmd(ecocNss, state.getEcocRenameNss()); + cmd.setDropTarget(false); + cmd.setCollectionUUID(state.getEcocUuid()); + + doRunCommand(opCtx.get(), ecocNss.db(), cmd); +} + +void doDropOperation(const CompactStructuredEncryptionDataState& state) { + auto ecocNss = state.getEcocRenameNss(); + Drop cmd(ecocNss); + cmd.setCollectionUUID(state.getEcocUuid()); + auto opCtx = cc().makeOperationContext(); + doRunCommand(opCtx.get(), ecocNss.db(), cmd); +} + +} // namespace + +boost::optional<BSONObj> CompactStructuredEncryptionDataCoordinator::reportForCurrentOp( + MongoProcessInterface::CurrentOpConnectionsMode connMode, + MongoProcessInterface::CurrentOpSessionsMode sessionMode) noexcept { + BSONObjBuilder bob; + bob.append("type", "op"); + bob.append("desc", "CompactStructuredEncryptionDataCoordinator"); + bob.append("op", "command"); + bob.append("nss", _doc.getId().getNss().ns()); + bob.append("ecocNss", _doc.getEcocNss().ns()); + bob.append("ecocUuid", _doc.getEcocUuid().toString()); + bob.append("ecocRenameNss", _doc.getEcocRenameNss().ns()); + bob.append("currentPhase", _doc.getPhase()); + bob.append("active", true); + return bob.obj(); +} + +void CompactStructuredEncryptionDataCoordinator::_enterPhase(Phase newPhase) { + StateDoc doc(_doc); + doc.setPhase(newPhase); + + LOGV2_DEBUG(6350490, + 2, + "Transitioning phase for CompactStructuredEncryptionDataCoordinator", + "nss"_attr = _doc.getId().getNss().ns(), + "ecocNss"_attr = _doc.getEcocNss().ns(), + "ecocUuid"_attr = _doc.getEcocUuid(), + "ecocRenameNss"_attr = _doc.getEcocRenameNss().ns(), + "compactionTokens"_attr = _doc.getCompactionTokens(), + "oldPhase"_attr = CompactStructuredEncryptionDataPhase_serializer(_doc.getPhase()), + "newPhase"_attr = CompactStructuredEncryptionDataPhase_serializer(newPhase)); + + if (_doc.getPhase() == Phase::kRenameEcocForCompact) { + _doc = _insertStateDocument(std::move(doc)); + return; + } + auto opCtx = cc().makeOperationContext(); + _doc = _updateStateDocument(opCtx.get(), std::move(doc)); +} + +ExecutorFuture<void> CompactStructuredEncryptionDataCoordinator::_runImpl( + std::shared_ptr<executor::ScopedTaskExecutor> executor, + const CancellationToken& token) noexcept { + return ExecutorFuture<void>(**executor) + .then(_executePhase(Phase::kRenameEcocForCompact, doRenameOperation)) + .then(_executePhase(Phase::kCompactStructuredEncryptionData, + [this, anchor = shared_from_this()](const auto&) { + // This will be implemented in a later phase. + ECOCStats ecocStats(1, 2); + ECStats eccStats(3, 4, 5, 6); + ECStats escStats(7, 8, 9, 10); + _response = CompactStats(ecocStats, eccStats, escStats); + })) + .then(_executePhase(Phase::kDropTempCollection, doDropOperation)); +} + +} // namespace mongo diff --git a/src/mongo/db/s/compact_structured_encryption_data_coordinator.h b/src/mongo/db/s/compact_structured_encryption_data_coordinator.h new file mode 100644 index 00000000000..cd5a2319250 --- /dev/null +++ b/src/mongo/db/s/compact_structured_encryption_data_coordinator.h @@ -0,0 +1,100 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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. + */ + +#pragma once + +#include <boost/optional.hpp> +#include <memory> + +#include "mongo/bson/bsonobj.h" +#include "mongo/db/commands/fle2_compact_gen.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/s/compact_structured_encryption_data_coordinator_gen.h" +#include "mongo/db/s/sharding_ddl_coordinator.h" + +namespace mongo { + +class CompactStructuredEncryptionDataCoordinator final : public ShardingDDLCoordinator { +public: + static constexpr auto kStateContext = "CompactStructuredEncryptionDataState"_sd; + using StateDoc = CompactStructuredEncryptionDataState; + using Phase = CompactStructuredEncryptionDataPhaseEnum; + + CompactStructuredEncryptionDataCoordinator(ShardingDDLCoordinatorService* service, + const BSONObj& doc) + : ShardingDDLCoordinator(service, doc), _doc(StateDoc::parse({kStateContext}, doc)) {} + + boost::optional<BSONObj> reportForCurrentOp( + MongoProcessInterface::CurrentOpConnectionsMode connMode, + MongoProcessInterface::CurrentOpSessionsMode sessionMode) noexcept final; + + CompactStructuredEncryptionDataCommandReply getResponse(OperationContext* opCtx) { + getCompletionFuture().get(opCtx); + invariant(_response); + return *_response; + } + + const NamespaceString& nss() const { + return _doc.getId().getNss(); + } + + void checkIfOptionsConflict(const BSONObj& doc) const final {} + +private: + void _enterPhase(Phase newPhase); + + template <typename Func> + auto _executePhase(const Phase& newPhase, Func&& func) { + return [=] { + const auto& currPhase = _doc.getPhase(); + if (currPhase > newPhase) { + return; + } + if (currPhase < newPhase) { + _enterPhase(newPhase); + } + + return func(_doc); + }; + } + +private: + ShardingDDLCoordinatorMetadata const& metadata() const final { + return _doc.getShardingDDLCoordinatorMetadata(); + } + + ExecutorFuture<void> _runImpl(std::shared_ptr<executor::ScopedTaskExecutor> executor, + const CancellationToken& token) noexcept final; + +private: + StateDoc _doc; + boost::optional<CompactStructuredEncryptionDataCommandReply> _response; +}; + +} // namespace mongo diff --git a/src/mongo/db/s/compact_structured_encryption_data_coordinator.idl b/src/mongo/db/s/compact_structured_encryption_data_coordinator.idl new file mode 100644 index 00000000000..a22a235495b --- /dev/null +++ b/src/mongo/db/s/compact_structured_encryption_data_coordinator.idl @@ -0,0 +1,68 @@ +# Copyright (C) 2021-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 +# <http://www.mongodb.com/licensing/server-side-public-license>. +# +# 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. +# + +global: + cpp_namespace: "mongo" + +imports: + - "mongo/idl/basic_types.idl" + - "mongo/db/commands/fle2_compact.idl" + - "mongo/db/s/sharding_ddl_coordinator.idl" + +enums: + CompactStructuredEncryptionDataPhase: + description: "The current phase of the compactStructuredEncryptionData pipeline" + type: string + values: + kRenameEcocForCompact: "rename-ecoc-for-compact" + kCompactStructuredEncryptionData: "compact-structured-encryption-data" + kDropTempCollection: "drop-temp-collection" + +structs: + CompactStructuredEncryptionDataState: + description: "Represents the state of the compactStructuredEncryptionData pipeline" + strict: true + chained_structs: + ShardingDDLCoordinatorMetadata: ShardingDDLCoordinatorMetadata + fields: + phase: + description: "Current phase" + type: CompactStructuredEncryptionDataPhase + default: kRenameEcocForCompact + ecocNss: + description: "Collection containing compaction metadata to perform compact with" + type: namespacestring + ecocUuid: + description: "UUID of the collection identified by ecocNss" + type: uuid + ecocRenameNss: + description: "Temporary name to use while performing compaction" + type: namespacestring + compactionTokens: + description: "Compaction tokens for the compact operation" + type: object_owned diff --git a/src/mongo/db/s/sharding_ddl_coordinator.idl b/src/mongo/db/s/sharding_ddl_coordinator.idl index eb0b7e88590..3a918da262d 100644 --- a/src/mongo/db/s/sharding_ddl_coordinator.idl +++ b/src/mongo/db/s/sharding_ddl_coordinator.idl @@ -58,6 +58,7 @@ enums: kCollMod: "collMod_V2" kReshardCollection: "reshardCollection" kReshardCollectionNoResilient: "reshardCollectionNoResilient" + kCompactStructuredEncryptionData: "compactStructuredEncryptionData" types: ForwardableOperationMetadata: diff --git a/src/mongo/db/s/sharding_ddl_coordinator_service.cpp b/src/mongo/db/s/sharding_ddl_coordinator_service.cpp index 60e288f2082..5fb5a6fb6ac 100644 --- a/src/mongo/db/s/sharding_ddl_coordinator_service.cpp +++ b/src/mongo/db/s/sharding_ddl_coordinator_service.cpp @@ -40,6 +40,7 @@ #include "mongo/db/pipeline/expression_context.h" #include "mongo/db/s/collmod_coordinator.h" #include "mongo/db/s/collmod_coordinator_pre60_compatible.h" +#include "mongo/db/s/compact_structured_encryption_data_coordinator.h" #include "mongo/db/s/create_collection_coordinator.h" #include "mongo/db/s/database_sharding_state.h" #include "mongo/db/s/drop_collection_coordinator.h" @@ -106,6 +107,10 @@ std::shared_ptr<ShardingDDLCoordinator> constructShardingDDLCoordinatorInstance( return std::make_shared<ReshardCollectionCoordinator_NORESILIENT>( service, std::move(initialState)); break; + case DDLCoordinatorTypeEnum::kCompactStructuredEncryptionData: + return std::make_shared<CompactStructuredEncryptionDataCoordinator>( + service, std::move(initialState)); + break; default: uasserted(ErrorCodes::BadValue, str::stream() diff --git a/src/mongo/db/s/shardsvr_compact_structured_encryption_data_command.cpp b/src/mongo/db/s/shardsvr_compact_structured_encryption_data_command.cpp new file mode 100644 index 00000000000..f777a7fd309 --- /dev/null +++ b/src/mongo/db/s/shardsvr_compact_structured_encryption_data_command.cpp @@ -0,0 +1,148 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand + +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/catalog/collection_catalog.h" +#include "mongo/db/catalog_raii.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/fle2_compact.h" +#include "mongo/db/commands/fle2_compact_gen.h" +#include "mongo/db/s/compact_structured_encryption_data_coordinator.h" +#include "mongo/db/s/compact_structured_encryption_data_coordinator_gen.h" +#include "mongo/logv2/log.h" +#include "mongo/s/cluster_commands_helpers.h" + +namespace mongo { +namespace { + +class _shardsvrCompactStructuredEncryptionDataCommand final + : public TypedCommand<_shardsvrCompactStructuredEncryptionDataCommand> { +public: + using Request = CompactStructuredEncryptionData; + using Reply = typename Request::Reply; + + _shardsvrCompactStructuredEncryptionDataCommand() + : TypedCommand("_shardsvrCompactStructuredEncryptionData"_sd) {} + + bool skipApiVersionCheck() const final { + // Internal command (server to server). + return true; + } + + std::string help() const final { + return "Internal command. Do not call directly. Compacts a ECOC collection."; + } + + bool adminOnly() const final { + return false; + } + + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + return AllowedOnSecondary::kNever; + } + + class Invocation final : public InvocationBase { + public: + using InvocationBase::InvocationBase; + + Reply typedRun(OperationContext* opCtx) { + uassert(6350499, + "Feature flag FLE2 is not enabled", + gFeatureFlagFLE2.isEnabledAndIgnoreFCV()); + + auto compact = makeRequest(opCtx); + if (!compact) { + // Nothing to do. + return {{{0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}}; + } + + return checked_pointer_cast<CompactStructuredEncryptionDataCoordinator>( + ShardingDDLCoordinatorService::getService(opCtx)->getOrCreateInstance( + opCtx, compact->toBSON())) + ->getResponse(opCtx); + } + + private: + boost::optional<CompactStructuredEncryptionDataState> makeRequest(OperationContext* opCtx) { + const auto& req = request(); + const auto& nss = req.getNamespace(); + + AutoGetCollection baseColl(opCtx, nss, MODE_IX); + uassert(ErrorCodes::NamespaceNotFound, + str::stream() << "Unknown collection: " << nss, + baseColl.getCollection()); + + auto namespaces = + uassertStatusOK(EncryptedStateCollectionsNamespaces::createFromDataCollection( + *(baseColl.getCollection().get()))); + + AutoGetCollection ecocColl(opCtx, namespaces.ecocNss, MODE_IX); + if (!ecocColl.getCollection()) { + return boost::none; + } + auto ecocUUID = ecocColl->uuid(); + + // Append UUID to rename namespace. + NamespaceString renameNss(namespaces.ecocRenameNss.db(), + str::stream() << namespaces.ecocRenameNss << '.' << ecocUUID); + + CompactStructuredEncryptionDataState compact; + compact.setShardingDDLCoordinatorMetadata( + {{nss, DDLCoordinatorTypeEnum::kCompactStructuredEncryptionData}}); + compact.setEcocNss(namespaces.ecocNss); + compact.setEcocUuid(std::move(ecocUUID)); + compact.setEcocRenameNss(renameNss); + compact.setCompactionTokens(req.getCompactionTokens().getOwned()); + + return compact; + } + + NamespaceString ns() const override { + return request().getNamespace(); + } + + bool supportsWriteConcern() const override { + return true; + } + + void doCheckAuthorization(OperationContext* opCtx) const override { + uassert(ErrorCodes::Unauthorized, + "Unauthorized", + AuthorizationSession::get(opCtx->getClient()) + ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), + ActionType::internal)); + } + }; + +} shardsvrCompactStructuredEncryptionDataCommand; + +} // namespace +} // namespace mongo diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index b9f96eb6a29..385824d19f6 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -57,6 +57,7 @@ env.Library( 'cluster_filemd5_cmd.cpp', 'cluster_find_and_modify_cmd.cpp', 'cluster_find_cmd_s.cpp', + 'cluster_fle2_compact_cmd.cpp', 'cluster_fsync_cmd.cpp', 'cluster_ftdc_commands.cpp', 'cluster_get_last_error_cmd.cpp', diff --git a/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp b/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp new file mode 100644 index 00000000000..b1cecbf555e --- /dev/null +++ b/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp @@ -0,0 +1,117 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/fle2_compact_gen.h" +#include "mongo/s/cluster_commands_helpers.h" +#include "mongo/s/grid.h" + +namespace mongo { +namespace { + +class ClusterCompactStructuredEncryptionDataCmd final + : public TypedCommand<ClusterCompactStructuredEncryptionDataCmd> { +public: + using Request = CompactStructuredEncryptionData; + using Reply = CompactStructuredEncryptionData::Reply; + + class Invocation final : public InvocationBase { + public: + using InvocationBase::InvocationBase; + + Reply typedRun(OperationContext* opCtx); + + private: + bool supportsWriteConcern() const final { + return false; + } + + void doCheckAuthorization(OperationContext* opCtx) const final { + auto* as = AuthorizationSession::get(opCtx->getClient()); + uassert(ErrorCodes::Unauthorized, + "Not authorized to compact structured encryption data", + as->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace(request().getNamespace()), + ActionType::compactStructuredEncryptionData)); + } + + NamespaceString ns() const final { + return request().getNamespace(); + } + }; + + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + return BasicCommand::AllowedOnSecondary::kNever; + } + + bool adminOnly() const final { + return false; + } +} clusterCompactStructuredEncryptionDataCmd; + +using Cmd = ClusterCompactStructuredEncryptionDataCmd; +Cmd::Reply Cmd::Invocation::typedRun(OperationContext* opCtx) { + auto nss = request().getNamespace(); + const auto dbInfo = + uassertStatusOK(Grid::get(opCtx)->catalogCache()->getDatabase(opCtx, nss.db())); + + // Rewrite command verb to _shardSvrCompactStructuredEnccryptionData. + auto cmd = request().toBSON({}); + BSONObjBuilder req; + for (const auto& elem : cmd) { + if (elem.fieldNameStringData() == Request::kCommandName) { + req.appendAs(elem, "_shardsvrCompactStructuredEncryptionData"); + } else { + req.append(elem); + } + } + + auto response = uassertStatusOK( + executeCommandAgainstDatabasePrimary( + opCtx, + nss.db(), + dbInfo, + CommandHelpers::appendMajorityWriteConcern(req.obj(), opCtx->getWriteConcern()), + ReadPreferenceSetting(ReadPreference::PrimaryOnly), + Shard::RetryPolicy::kIdempotent) + .swResponse); + + BSONObjBuilder result; + CommandHelpers::filterCommandReplyForPassthrough(response.data, &result); + + auto reply = result.obj(); + uassertStatusOK(getStatusFromCommandResult(reply)); + return Reply::parse({Request::kCommandName}, reply.removeField("ok"_sd)); +} + +} // namespace +} // namespace mongo |