From 30f415df521fc9b9a04801464f01880e412decce Mon Sep 17 00:00:00 2001 From: Gregory Noma Date: Tue, 25 Jan 2022 21:01:10 +0000 Subject: SERVER-62455 Add `collectionUUID` parameter to `renameCollection` command --- ..._stmt_txn_jscore_passthrough_with_migration.yml | 4 +- jstests/core/collection_uuid_rename_collection.js | 81 ++++++++++++++++++++++ jstests/core/rename_collection.js | 1 + jstests/libs/parallelTester.js | 1 + src/mongo/db/catalog/rename_collection.cpp | 7 ++ src/mongo/db/catalog/rename_collection.h | 1 + src/mongo/db/commands/rename_collection.idl | 4 ++ src/mongo/db/commands/rename_collection_cmd.cpp | 1 + src/mongo/db/s/rename_collection_coordinator.cpp | 10 +++ .../db/s/rename_collection_participant_service.cpp | 3 +- .../s/commands/cluster_rename_collection_cmd.cpp | 1 + src/mongo/s/request_types/sharded_ddl_commands.idl | 4 ++ 12 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 jstests/core/collection_uuid_rename_collection.js diff --git a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml index 6635d2da9ea..aacde719bd6 100644 --- a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml +++ b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml @@ -273,9 +273,6 @@ selector: # Does not support tojson of command objects. - jstests/core/SERVER-23626.js - # This suite is using zones for migrations but the following test is expecting no entries in config.tags - - jstests/core/rename_collection.js - exclude_with_any_tags: # "Cowardly refusing to override read concern of command: ..." - assumes_read_concern_unchanged @@ -307,6 +304,7 @@ selector: # startParallelShell()" - uses_parallel_shell - does_not_support_causal_consistency + - does_not_support_zones executor: archive: diff --git a/jstests/core/collection_uuid_rename_collection.js b/jstests/core/collection_uuid_rename_collection.js new file mode 100644 index 00000000000..3c3730e104b --- /dev/null +++ b/jstests/core/collection_uuid_rename_collection.js @@ -0,0 +1,81 @@ +/** + * Tests the collectionUUID parameter of the renameCollection command. + * + * @tags: [ + * does_not_support_zones, + * featureFlagCommandsAcceptCollectionUUID, + * no_selinux, + * tenant_migration_incompatible, + * ] + */ +(function() { +'use strict'; + +const testDB = db.getSiblingDB(jsTestName()); +assert.commandWorked(testDB.dropDatabase()); + +const coll = testDB.coll; +const coll2 = testDB.coll_2; +const coll3 = testDB.coll_3; + +const resetColls = function() { + coll.drop(); + coll2.drop(); + coll3.drop(); + + assert.commandWorked(coll.insert({_id: 0})); + assert.commandWorked(coll2.insert({_id: 1})); +}; + +const uuid = function() { + return assert.commandWorked(testDB.runCommand({listCollections: 1})) + .cursor.firstBatch.find(c => c.name === coll.getName()) + .info.uuid; +}; + +// The command succeeds when the correct UUID is provided. +resetColls(); +assert.commandWorked(testDB.adminCommand({ + renameCollection: coll.getFullName(), + to: coll3.getFullName(), + dropTarget: true, + collectionUUID: uuid(), +})); + +// The command fails when the provided UUID does not correspond to an existing collection. +resetColls(); +const nonexistentUUID = UUID(); +let res = assert.commandFailedWithCode(testDB.adminCommand({ + renameCollection: coll.getFullName(), + to: coll3.getFullName(), + dropTarget: true, + collectionUUID: nonexistentUUID, +}), + ErrorCodes.CollectionUUIDMismatch); +assert.eq(res.collectionUUID, nonexistentUUID); +assert.eq(res.actualNamespace, ""); + +// The command fails when the provided UUID corresponds to a different collection. +resetColls(); +res = assert.commandFailedWithCode(testDB.adminCommand({ + renameCollection: coll2.getFullName(), + to: coll3.getFullName(), + dropTarget: true, + collectionUUID: uuid(), +}), + ErrorCodes.CollectionUUIDMismatch); +assert.eq(res.collectionUUID, uuid()); +assert.eq(res.actualNamespace, coll.getFullName()); + +// The collectionUUID parameter cannot be provided when renaming a collection between databases. +const otherDBColl = db.getSiblingDB(jsTestName() + '_2').coll; +otherDBColl.drop(); +assert.commandWorked(otherDBColl.insert({_id: 3})); +assert.commandFailedWithCode(testDB.adminCommand({ + renameCollection: coll.getFullName(), + to: otherDBColl.getFullName(), + dropTarget: true, + collectionUUID: uuid(), +}), + [ErrorCodes.InvalidOptions, ErrorCodes.CommandFailed]); +})(); diff --git a/jstests/core/rename_collection.js b/jstests/core/rename_collection.js index c47fdec0153..ee67447e11c 100644 --- a/jstests/core/rename_collection.js +++ b/jstests/core/rename_collection.js @@ -3,6 +3,7 @@ * * @tags: [ * assumes_no_implicit_collection_creation_after_drop, + * does_not_support_zones, * requires_non_retryable_commands, * ] */ diff --git a/jstests/libs/parallelTester.js b/jstests/libs/parallelTester.js index 1574c3a29f7..ad8ce6c98a0 100644 --- a/jstests/libs/parallelTester.js +++ b/jstests/libs/parallelTester.js @@ -237,6 +237,7 @@ if (typeof _threadInject != "undefined") { "collection_uuid_find.js", "collection_uuid_write_commands.js", "collection_uuid_coll_mod.js", + "collection_uuid_rename_collection.js", ]); // Get files, including files in subdirectories. diff --git a/src/mongo/db/catalog/rename_collection.cpp b/src/mongo/db/catalog/rename_collection.cpp index 3695a67363e..806bc5c69a3 100644 --- a/src/mongo/db/catalog/rename_collection.cpp +++ b/src/mongo/db/catalog/rename_collection.cpp @@ -35,6 +35,7 @@ #include "mongo/bson/unordered_fields_bsonobj_comparator.h" #include "mongo/db/catalog/collection_catalog.h" +#include "mongo/db/catalog/collection_uuid_mismatch.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/catalog/document_validation.h" #include "mongo/db/catalog/drop_collection.h" @@ -318,6 +319,8 @@ Status renameCollectionWithinDB(OperationContext* opCtx, const auto sourceColl = catalog->lookupCollectionByNamespace(opCtx, source); const auto targetColl = catalog->lookupCollectionByNamespace(opCtx, target); + checkCollectionUUIDMismatch(opCtx, sourceColl, options.expectedSourceUUID); + AutoStatsTracker statsTracker( opCtx, source, @@ -459,6 +462,10 @@ Status renameBetweenDBs(OperationContext* opCtx, str::stream() << "renameBetweenDBs not supported in multi-document transaction: source: " << source << "; target: " << target); + uassert(ErrorCodes::InvalidOptions, + "Cannot provide an expected source collection UUID when renaming between databases", + !options.expectedSourceUUID); + boost::optional sourceDbLock; boost::optional sourceCollLock; if (!opCtx->lockState()->isCollectionLockedForMode(source, MODE_S)) { diff --git a/src/mongo/db/catalog/rename_collection.h b/src/mongo/db/catalog/rename_collection.h index 4dee5f8731f..1f46262aca2 100644 --- a/src/mongo/db/catalog/rename_collection.h +++ b/src/mongo/db/catalog/rename_collection.h @@ -48,6 +48,7 @@ class OpTime; * temporariness. */ struct RenameCollectionOptions { + boost::optional expectedSourceUUID; bool dropTarget = false; bool stayTemp = false; bool markFromMigrate = false; diff --git a/src/mongo/db/commands/rename_collection.idl b/src/mongo/db/commands/rename_collection.idl index ba7c19699d1..6ec0e7b97e7 100644 --- a/src/mongo/db/commands/rename_collection.idl +++ b/src/mongo/db/commands/rename_collection.idl @@ -55,3 +55,7 @@ commands: before the rename." type: bool default: false + collectionUUID: + description: "The expected UUID of the collection." + type: uuid + optional: true diff --git a/src/mongo/db/commands/rename_collection_cmd.cpp b/src/mongo/db/commands/rename_collection_cmd.cpp index 7f5565cdba9..7a3bcd029ab 100644 --- a/src/mongo/db/commands/rename_collection_cmd.cpp +++ b/src/mongo/db/commands/rename_collection_cmd.cpp @@ -102,6 +102,7 @@ public: ErrorCodes::IllegalOperation, "Can't rename a collection to itself", fromNss != toNss); RenameCollectionOptions options; + options.expectedSourceUUID = renameRequest.getCollectionUUID(); options.dropTarget = renameRequest.getDropTarget(); options.stayTemp = renameRequest.getStayTemp(); validateAndRunRenameCollection( diff --git a/src/mongo/db/s/rename_collection_coordinator.cpp b/src/mongo/db/s/rename_collection_coordinator.cpp index 520faa8289b..ea807af46c5 100644 --- a/src/mongo/db/s/rename_collection_coordinator.cpp +++ b/src/mongo/db/s/rename_collection_coordinator.cpp @@ -34,6 +34,7 @@ #include "mongo/db/s/rename_collection_coordinator.h" #include "mongo/db/catalog/collection_catalog.h" +#include "mongo/db/catalog/collection_uuid_mismatch.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/db_raii.h" #include "mongo/db/ops/insert.h" @@ -178,9 +179,18 @@ ExecutorFuture RenameCollectionCoordinator::_runImpl( fromNss.db() == toNss.db()); _doc.setOptShardedCollInfo(optSourceCollType); } else if (fromNss.db() != toNss.db()) { + uassert(ErrorCodes::InvalidOptions, + "Cannot provide an expected source collection UUID when renaming " + "between databases", + !_doc.getCollectionUUID()); sharding_ddl_util::checkDbPrimariesOnTheSameShard(opCtx, fromNss, toNss); } + { + AutoGetCollection coll{opCtx, fromNss, MODE_IS}; + checkCollectionUUIDMismatch(opCtx, *coll, _doc.getCollectionUUID()); + } + // Make sure the target namespace is not a view { Lock::DBLock dbLock(opCtx, toNss.db(), MODE_IS); diff --git a/src/mongo/db/s/rename_collection_participant_service.cpp b/src/mongo/db/s/rename_collection_participant_service.cpp index 62da2326850..da849f43f72 100644 --- a/src/mongo/db/s/rename_collection_participant_service.cpp +++ b/src/mongo/db/s/rename_collection_participant_service.cpp @@ -279,7 +279,8 @@ SemiFuture RenameParticipantInstance::run( auto* opCtx = opCtxHolder.get(); _doc.getForwardableOpMetadata().setOn(opCtx); - const RenameCollectionOptions options{_doc.getDropTarget(), _doc.getStayTemp()}; + const RenameCollectionOptions options{ + _doc.getCollectionUUID(), _doc.getDropTarget(), _doc.getStayTemp()}; renameOrDropTarget( opCtx, fromNss(), toNss(), options, _doc.getSourceUUID(), _doc.getTargetUUID()); diff --git a/src/mongo/s/commands/cluster_rename_collection_cmd.cpp b/src/mongo/s/commands/cluster_rename_collection_cmd.cpp index 3df3b928292..6146de29db4 100644 --- a/src/mongo/s/commands/cluster_rename_collection_cmd.cpp +++ b/src/mongo/s/commands/cluster_rename_collection_cmd.cpp @@ -77,6 +77,7 @@ public: RenameCollectionRequest renameCollReq(request().getTo()); renameCollReq.setDropTarget(request().getDropTarget()); renameCollReq.setStayTemp(request().getStayTemp()); + renameCollReq.setCollectionUUID(request().getCollectionUUID()); ShardsvrRenameCollection renameCollRequest(fromNss); renameCollRequest.setDbName(fromNss.db()); diff --git a/src/mongo/s/request_types/sharded_ddl_commands.idl b/src/mongo/s/request_types/sharded_ddl_commands.idl index 878c9f142c7..03293b1971e 100644 --- a/src/mongo/s/request_types/sharded_ddl_commands.idl +++ b/src/mongo/s/request_types/sharded_ddl_commands.idl @@ -78,6 +78,10 @@ structs: description: "If true, the original collection will remain temp if it was temp before the rename." default: false + collectionUUID: + type: uuid + description: "The expected UUID of the collection." + optional: true RenameCollectionResponse: description: "Response for the rename collection command" -- cgit v1.2.1